1use crate::distributor::*;
2use crate::error::ContractError;
3use crate::error::ContractError::InvalidConfigurationValue;
4use crate::external::*;
5use crate::msg::*;
6use crate::state::*;
7#[cfg(not(feature = "library"))]
8use cosmwasm_std::entry_point;
9use cosmwasm_std::{
10 has_coins, to_binary, Addr, Attribute, Binary, Coin, Deps, DepsMut, Empty, Env, MessageInfo,
11 Order, QuerierWrapper, Response, StdError, StdResult, Storage, Uint64,
12};
13use croncat_sdk_agents::msg::{
14 AgentInfo, AgentResponse, AgentTaskResponse, ApprovedAgentAddresses, GetAgentIdsResponse,
15 TaskStats, UpdateConfig,
16};
17use croncat_sdk_agents::types::{Agent, AgentNominationStatus, AgentStatus, Config};
18use croncat_sdk_core::internal_messages::agents::{AgentOnTaskCompleted, AgentOnTaskCreated};
19use croncat_sdk_core::types::{DEFAULT_PAGINATION_FROM_INDEX, DEFAULT_PAGINATION_LIMIT};
20use cw2::set_contract_version;
21use std::cmp::min;
22
23pub(crate) const CONTRACT_NAME: &str = "crate:croncat-agents";
24pub(crate) const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION");
25
26#[cfg_attr(not(feature = "library"), entry_point)]
27pub fn instantiate(
28 deps: DepsMut,
29 _env: Env,
30 info: MessageInfo,
31 msg: InstantiateMsg,
32) -> Result<Response, ContractError> {
33 let InstantiateMsg {
34 pause_admin,
35 version,
36 croncat_manager_key,
37 croncat_tasks_key,
38 agent_nomination_duration,
39 min_tasks_per_agent,
40 min_coins_for_agent_registration,
41 agents_eject_threshold,
42 min_active_agent_count,
43 public_registration,
44 allowed_agents,
45 } = msg;
46
47 validate_config_non_zero_u16(agent_nomination_duration, "agent_nomination_duration")?;
48 validate_config_non_zero_u16(min_active_agent_count, "min_active_agent_count")?;
49 validate_config_non_zero_u64(min_tasks_per_agent, "min_tasks_per_agent")?;
50 validate_config_non_zero_u64(agents_eject_threshold, "agents_eject_threshold")?;
51 validate_config_non_zero_u64(
52 min_coins_for_agent_registration,
53 "min_coins_for_agent_registration",
54 )?;
55
56 let validated_allowed_agents = if let Some(agent_addrs) = &allowed_agents {
58 map_validate(&deps, agent_addrs)?
59 } else {
60 vec![]
61 };
62
63 let owner_addr = info.sender.clone();
64
65 if !(61usize..=74usize).contains(&pause_admin.to_string().len()) {
71 return Err(ContractError::InvalidPauseAdmin {});
72 }
73
74 let config = &Config {
75 min_tasks_per_agent: min_tasks_per_agent.unwrap_or(DEFAULT_MIN_TASKS_PER_AGENT),
76 croncat_factory_addr: info.sender,
77 croncat_manager_key,
78 croncat_tasks_key,
79 agent_nomination_block_duration: agent_nomination_duration
80 .unwrap_or(DEFAULT_NOMINATION_BLOCK_DURATION),
81 owner_addr,
82 pause_admin,
83 agents_eject_threshold: agents_eject_threshold.unwrap_or(DEFAULT_AGENTS_EJECT_THRESHOLD),
84 min_coins_for_agent_registration: min_coins_for_agent_registration
85 .unwrap_or(DEFAULT_MIN_COINS_FOR_AGENT_REGISTRATION),
86 min_active_agent_count: min_active_agent_count.unwrap_or(DEFAULT_MIN_ACTIVE_AGENT_COUNT),
87 public_registration,
88 };
89
90 if !public_registration {
93 for agent_addr in validated_allowed_agents {
94 APPROVED_AGENTS
95 .save(deps.storage, &agent_addr, &Empty {})
96 .unwrap();
97 }
98 }
99
100 CONFIG.save(deps.storage, config)?;
101 PAUSED.save(deps.storage, &false)?;
102 AGENTS_ACTIVE.save(deps.storage, &vec![])?; set_contract_version(
104 deps.storage,
105 CONTRACT_NAME,
106 version.unwrap_or_else(|| CONTRACT_VERSION.to_string()),
107 )?;
108 AGENT_NOMINATION_STATUS.save(
109 deps.storage,
110 &AgentNominationStatus {
111 start_height_of_nomination: None,
112 tasks_created_from_last_nomination: 0,
113 },
114 )?;
115
116 Ok(Response::new()
117 .add_attribute("action", "instantiate")
118 .add_attribute("owner", config.owner_addr.to_string()))
119}
120
121#[cfg_attr(not(feature = "library"), entry_point)]
122pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
123 match msg {
124 QueryMsg::GetAgent { account_id } => to_binary(&query_get_agent(deps, env, account_id)?),
125 QueryMsg::GetAgentIds { from_index, limit } => {
126 to_binary(&query_get_agent_ids(deps, from_index, limit)?)
127 }
128 QueryMsg::GetApprovedAgentAddresses { from_index, limit } => to_binary(
129 &query_get_approved_agent_addresses(deps, from_index, limit)?,
130 ),
131 QueryMsg::GetAgentTasks { account_id } => {
132 to_binary(&query_get_agent_tasks(deps, env, account_id)?)
133 }
134 QueryMsg::Config {} => to_binary(&CONFIG.load(deps.storage)?),
135 QueryMsg::Paused {} => to_binary(&PAUSED.load(deps.storage)?),
136 }
137}
138
139#[cfg_attr(not(feature = "library"), entry_point)]
140pub fn execute(
141 deps: DepsMut,
142 env: Env,
143 info: MessageInfo,
144 msg: ExecuteMsg,
145) -> Result<Response, ContractError> {
146 match msg {
147 ExecuteMsg::RegisterAgent { payable_account_id } => {
148 register_agent(deps, info, env, payable_account_id)
149 }
150 ExecuteMsg::UnregisterAgent { from_behind } => {
151 unregister_agent(deps.storage, &deps.querier, &info.sender, from_behind)
152 }
153 ExecuteMsg::UpdateAgent { payable_account_id } => {
154 update_agent(deps, info, env, payable_account_id)
155 }
156 ExecuteMsg::CheckInAgent {} => accept_nomination_agent(deps, info, env),
157 ExecuteMsg::OnTaskCreated(msg) => on_task_created(env, deps, info, msg),
158 ExecuteMsg::OnTaskCompleted(msg) => on_task_completed(deps, info, msg),
159 ExecuteMsg::UpdateConfig { config } => execute_update_config(deps, info, config),
160 ExecuteMsg::Tick {} => execute_tick(deps, env),
161 ExecuteMsg::PauseContract {} => execute_pause(deps, info),
162 ExecuteMsg::UnpauseContract {} => execute_unpause(deps, info),
163 ExecuteMsg::AddAgentToWhitelist { agent_address } => {
164 execute_add_agent_to_whitelist(env, deps, info, agent_address)
165 }
166 ExecuteMsg::RemoveAgentFromWhitelist { agent_address } => {
167 execute_remove_agent_from_whitelist(env, deps, info, agent_address)
168 }
169 }
170}
171
172fn query_get_agent(deps: Deps, env: Env, account_id: String) -> StdResult<AgentResponse> {
173 let account_id = deps.api.addr_validate(&account_id)?;
174
175 let agent = AGENTS.may_load(deps.storage, &account_id)?;
176
177 let a = if let Some(a) = agent {
178 a
179 } else {
180 return Ok(AgentResponse { agent: None });
181 };
182
183 let config: Config = CONFIG.load(deps.storage)?;
184 let rewards =
185 croncat_manager_contract::query_agent_rewards(&deps.querier, &config, account_id.as_str())?;
186 let agent_status = get_agent_status(deps.storage, env, &account_id)
187 .map_err(|err| StdError::GenericErr {
189 msg: err.to_string(),
190 })?;
191
192 let stats = AGENT_STATS
193 .may_load(deps.storage, &account_id)?
194 .unwrap_or_default();
195 let agent_response = AgentResponse {
196 agent: Some(AgentInfo {
197 status: agent_status,
198 payable_account_id: a.payable_account_id,
199 balance: rewards,
200 register_start: a.register_start,
201 last_executed_slot: stats.last_executed_slot,
202 completed_block_tasks: Uint64::from(stats.completed_block_tasks),
203 completed_cron_tasks: Uint64::from(stats.completed_cron_tasks),
204 missed_blocked_tasks: Uint64::from(stats.missed_blocked_tasks),
205 missed_cron_tasks: Uint64::from(stats.missed_cron_tasks),
206 }),
207 };
208 Ok(agent_response)
209}
210
211fn query_get_agent_ids(
213 deps: Deps,
214 from_index: Option<u64>,
215 limit: Option<u64>,
216) -> StdResult<GetAgentIdsResponse> {
217 let active_loaded: Vec<Addr> = AGENTS_ACTIVE.load(deps.storage)?;
218 let active = active_loaded
219 .into_iter()
220 .skip(from_index.unwrap_or(DEFAULT_PAGINATION_FROM_INDEX) as usize)
221 .take(limit.unwrap_or(DEFAULT_PAGINATION_LIMIT) as usize)
222 .collect();
223 let pending: Vec<Addr> = AGENTS_PENDING
224 .iter(deps.storage)?
225 .skip(from_index.unwrap_or(DEFAULT_PAGINATION_FROM_INDEX) as usize)
226 .take(limit.unwrap_or(DEFAULT_PAGINATION_LIMIT) as usize)
227 .collect::<StdResult<Vec<Addr>>>()?;
228
229 Ok(GetAgentIdsResponse { active, pending })
230}
231
232fn query_get_approved_agent_addresses(
235 deps: Deps,
236 from_index: Option<u64>,
237 limit: Option<u64>,
238) -> StdResult<ApprovedAgentAddresses> {
239 let agent_addresses = APPROVED_AGENTS
240 .keys(deps.storage, None, None, Order::Ascending)
241 .skip(from_index.unwrap_or(DEFAULT_PAGINATION_FROM_INDEX) as usize)
242 .take(limit.unwrap_or(DEFAULT_PAGINATION_LIMIT) as usize)
243 .collect::<Result<Vec<Addr>, StdError>>();
244
245 Ok(ApprovedAgentAddresses {
246 approved_addresses: agent_addresses.unwrap(),
247 })
248}
249
250fn query_get_agent_tasks(deps: Deps, env: Env, account_id: String) -> StdResult<AgentTaskResponse> {
251 let account_id = deps.api.addr_validate(&account_id)?;
252 let active = AGENTS_ACTIVE.load(deps.storage)?;
253 if !active.contains(&account_id) {
254 return Ok(AgentTaskResponse {
255 stats: TaskStats {
256 num_cron_tasks: Uint64::zero(),
257 num_block_tasks: Uint64::zero(),
258 },
259 });
260 }
261 let config: Config = CONFIG.load(deps.storage)?;
262
263 let (block_slots, cron_slots) = croncat_tasks_contract::query_tasks_slots(deps, &config)?;
264 if block_slots == 0 && cron_slots == 0 {
265 return Ok(AgentTaskResponse {
266 stats: TaskStats {
267 num_cron_tasks: Uint64::zero(),
268 num_block_tasks: Uint64::zero(),
269 },
270 });
271 }
272 AGENT_TASK_DISTRIBUTOR
273 .get_agent_tasks(
274 &deps,
275 &env,
276 account_id,
277 (Some(block_slots), Some(cron_slots)),
278 )
279 .map_err(|err| StdError::generic_err(err.to_string()))
280}
281
282fn register_agent(
289 deps: DepsMut,
290 info: MessageInfo,
291 env: Env,
292 payable_account_id: Option<String>,
293) -> Result<Response, ContractError> {
294 if !info.funds.is_empty() {
295 return Err(ContractError::NoFundsShouldBeAttached);
296 }
297 if PAUSED.load(deps.storage)? {
298 return Err(ContractError::ContractPaused);
299 }
300 let c = CONFIG.load(deps.storage)?;
301 let account = info.sender;
302
303 if !c.public_registration && !APPROVED_AGENTS.has(deps.storage, &account) {
305 return Err(ContractError::UnapprovedAgent {});
306 }
307
308 let agent_wallet_balances = deps.querier.query_all_balances(account.clone())?;
311
312 let manager_config = croncat_manager_contract::query_manager_config(&deps.as_ref(), &c)?;
314
315 let agents_needs_coin = Coin::new(
316 c.min_coins_for_agent_registration.into(),
317 manager_config.native_denom,
318 );
319 if !has_coins(&agent_wallet_balances, &agents_needs_coin) || agent_wallet_balances.is_empty() {
320 return Err(ContractError::InsufficientFunds {
321 amount_needed: agents_needs_coin,
322 });
323 }
324
325 let payable_id = if let Some(addr) = payable_account_id {
326 deps.api.addr_validate(&addr)?
327 } else {
328 account.clone()
329 };
330
331 let mut active_agents_vec: Vec<Addr> = AGENTS_ACTIVE
332 .may_load(deps.storage)?
333 .ok_or(ContractError::NoActiveAgents)?;
334 let total_agents = active_agents_vec.len();
335 let agent_status = if total_agents == 0 {
336 active_agents_vec.push(account.clone());
337 AGENTS_ACTIVE.save(deps.storage, &active_agents_vec)?;
338 AgentStatus::Active
339 } else {
340 AGENTS_PENDING.push_back(deps.storage, &account)?;
341 AgentStatus::Pending
342 };
343
344 let storage = deps.storage;
345 AGENTS.update(
346 storage,
347 &account,
348 |a: Option<Agent>| -> Result<_, ContractError> {
349 match a {
350 Some(_) => Err(ContractError::AgentAlreadyRegistered),
352 None => {
353 Ok(Agent {
354 payable_account_id: payable_id,
355 register_start: env.block.time,
357 })
358 }
359 }
360 },
361 )?;
362 AGENT_STATS.save(
363 storage,
364 &account,
365 &AgentStats {
366 last_executed_slot: env.block.height,
367 completed_block_tasks: 0,
368 completed_cron_tasks: 0,
369 missed_blocked_tasks: 0,
370 missed_cron_tasks: 0,
371 },
372 )?;
373 Ok(Response::new()
374 .add_attribute("action", "register_agent")
375 .add_attribute("agent_status", agent_status.to_string()))
376}
377
378fn update_agent(
380 deps: DepsMut,
381 info: MessageInfo,
382 _env: Env,
383 payable_account_id: String,
384) -> Result<Response, ContractError> {
385 let payable_account_id = deps.api.addr_validate(&payable_account_id)?;
386 if PAUSED.load(deps.storage)? {
387 return Err(ContractError::ContractPaused);
388 }
389
390 AGENTS.update(
391 deps.storage,
392 &info.sender,
393 |a: Option<Agent>| -> Result<_, ContractError> {
394 match a {
395 Some(agent) => {
396 let mut ag = agent;
397 ag.payable_account_id = payable_account_id;
398 Ok(ag)
399 }
400 None => Err(ContractError::AgentNotRegistered {}),
401 }
402 },
403 )?;
404
405 Ok(Response::new().add_attribute("action", "update_agent"))
406}
407
408fn accept_nomination_agent(
410 deps: DepsMut,
411 info: MessageInfo,
412 env: Env,
413) -> Result<Response, ContractError> {
414 let c: Config = CONFIG.load(deps.storage)?;
416
417 let mut active_agents: Vec<Addr> = AGENTS_ACTIVE.load(deps.storage)?;
418 let mut pending_queue_iter = AGENTS_PENDING.iter(deps.storage)?;
419 let agent_position = pending_queue_iter
422 .position(|a| a.map_or_else(|_| false, |v| info.sender == v))
423 .ok_or(ContractError::AgentNotPending)?;
424 let agent_nomination_status = AGENT_NOMINATION_STATUS.load(deps.storage)?;
425 if active_agents.is_empty() && agent_position == 0 {
427 active_agents.push(info.sender.clone());
428 AGENTS_ACTIVE.save(deps.storage, &active_agents)?;
429
430 AGENTS_PENDING.pop_front(deps.storage)?;
431 AGENT_NOMINATION_STATUS.save(
432 deps.storage,
433 &AgentNominationStatus {
434 start_height_of_nomination: None,
435 tasks_created_from_last_nomination: 0,
436 },
437 )?;
438 return Ok(Response::new()
439 .add_attribute("action", "accept_nomination_agent")
440 .add_attribute("new_agent", info.sender.as_str()));
441 }
442
443 let max_index = max_agent_nomination_index(&c, env, agent_nomination_status)?
449 .ok_or(ContractError::TryLaterForNomination)?;
450 let kicked_agents = if agent_position as u64 <= max_index {
451 let kicked_agents: Vec<Addr> = {
454 let mut kicked = Vec::with_capacity(agent_position);
455 for _ in 0..=agent_position {
456 let agent = AGENTS_PENDING.pop_front(deps.storage)?;
457 let kicked_agent;
459 unsafe {
460 kicked_agent = agent.unwrap_unchecked();
461 }
462 kicked.push(kicked_agent);
463 }
464 kicked
465 };
466
467 active_agents.push(info.sender.clone());
469 AGENTS_ACTIVE.save(deps.storage, &active_agents)?;
470
471 AGENT_NOMINATION_STATUS.save(
474 deps.storage,
475 &AgentNominationStatus {
476 start_height_of_nomination: None,
477 tasks_created_from_last_nomination: 0,
478 },
479 )?;
480 kicked_agents
481 } else {
482 return Err(ContractError::TryLaterForNomination);
483 };
484 Ok(Response::new()
486 .add_attribute("action", "accept_nomination_agent")
487 .add_attribute("new_agent", info.sender.as_str())
488 .add_attribute("kicked_agents", format!("{kicked_agents:?}")))
489}
490
491fn unregister_agent(
495 storage: &mut dyn Storage,
496 querier: &QuerierWrapper<Empty>,
497 agent_id: &Addr,
498 from_behind: Option<bool>,
499) -> Result<Response, ContractError> {
500 if PAUSED.load(storage)? {
501 return Err(ContractError::ContractPaused);
502 }
503 let config: Config = CONFIG.load(storage)?;
504 let agent = AGENTS
505 .may_load(storage, agent_id)?
506 .ok_or(ContractError::AgentNotRegistered {})?;
507
508 let mut active_agents: Vec<Addr> = AGENTS_ACTIVE.load(storage)?;
510 if let Some(index) = active_agents.iter().position(|addr| addr == agent_id) {
511 AGENT_STATS.remove(storage, agent_id);
513 active_agents.remove(index);
514 AGENTS_ACTIVE.save(storage, &active_agents)?;
515 } else {
516 let mut return_those_agents: Vec<Addr> =
519 Vec::with_capacity((AGENTS_PENDING.len(storage)? / 2) as usize);
520 if from_behind.unwrap_or(false) {
521 while let Some(addr) = AGENTS_PENDING.pop_front(storage)? {
522 if addr.eq(agent_id) {
523 break;
524 } else {
525 return_those_agents.push(addr);
526 }
527 }
528 for ag in return_those_agents.iter().rev() {
529 AGENTS_PENDING.push_front(storage, ag)?;
530 }
531 } else {
532 while let Some(addr) = AGENTS_PENDING.pop_back(storage)? {
533 if addr.eq(agent_id) {
534 break;
535 } else {
536 return_those_agents.push(addr);
537 }
538 }
539 for ag in return_those_agents.iter().rev() {
540 AGENTS_PENDING.push_back(storage, ag)?;
541 }
542 }
543 }
544 let msg = croncat_manager_contract::create_withdraw_rewards_submsg(
545 querier,
546 &config,
547 agent_id.as_str(),
548 agent.payable_account_id.to_string(),
549 )?;
550 AGENTS.remove(storage, agent_id);
551
552 let responses = Response::new()
553 .add_message(msg)
555 .add_attribute("action", "unregister_agent")
556 .add_attribute("account_id", agent_id);
557
558 Ok(responses)
559}
560
561pub fn execute_update_config(
562 deps: DepsMut,
563 info: MessageInfo,
564 msg: UpdateConfig,
565) -> Result<Response, ContractError> {
566 let UpdateConfig {
568 croncat_manager_key,
569 croncat_tasks_key,
570 min_tasks_per_agent,
571 agent_nomination_duration,
572 min_coins_for_agent_registration,
573 agents_eject_threshold,
574 min_active_agent_count,
575 public_registration,
576 } = msg;
577
578 CONFIG.update(deps.storage, |config| {
579 validate_config_non_zero_u16(agent_nomination_duration, "agent_nomination_duration")?;
580 validate_config_non_zero_u16(min_active_agent_count, "min_active_agent_count")?;
581 validate_config_non_zero_u64(min_tasks_per_agent, "min_tasks_per_agent")?;
582 validate_config_non_zero_u64(agents_eject_threshold, "agents_eject_threshold")?;
583 validate_config_non_zero_u64(
584 min_coins_for_agent_registration,
585 "min_coins_for_agent_registration",
586 )?;
587
588 if info.sender != config.owner_addr {
589 return Err(ContractError::Unauthorized {});
590 }
591
592 if config.public_registration && public_registration == Some(false) {
595 return Err(ContractError::DecentralizationEnabled {});
596 }
597
598 let new_config = Config {
599 owner_addr: config.owner_addr,
600 pause_admin: config.pause_admin,
601 croncat_factory_addr: config.croncat_factory_addr,
602 croncat_manager_key: croncat_manager_key.unwrap_or(config.croncat_manager_key),
603 croncat_tasks_key: croncat_tasks_key.unwrap_or(config.croncat_tasks_key),
604 min_tasks_per_agent: min_tasks_per_agent.unwrap_or(config.min_tasks_per_agent),
605 agent_nomination_block_duration: agent_nomination_duration
606 .unwrap_or(config.agent_nomination_block_duration),
607 min_coins_for_agent_registration: min_coins_for_agent_registration
608 .unwrap_or(DEFAULT_MIN_COINS_FOR_AGENT_REGISTRATION),
609 agents_eject_threshold: agents_eject_threshold
610 .unwrap_or(DEFAULT_AGENTS_EJECT_THRESHOLD),
611 min_active_agent_count: min_active_agent_count
612 .unwrap_or(DEFAULT_MIN_ACTIVE_AGENT_COUNT),
613 public_registration: public_registration.unwrap_or(config.public_registration),
614 };
615 Ok(new_config)
616 })?;
617
618 if public_registration == Some(true) {
621 APPROVED_AGENTS.clear(deps.storage);
622 }
623
624 Ok(Response::new().add_attribute("action", "update_config"))
625}
626
627#[allow(clippy::op_ref)]
630fn get_agent_status(
631 storage: &dyn Storage,
632 env: Env,
633 account_id: &Addr,
634) -> Result<AgentStatus, ContractError> {
635 let c: Config = CONFIG.load(storage)?;
636 let active = AGENTS_ACTIVE.load(storage)?;
637
638 let mut pending_iter = AGENTS_PENDING.iter(storage)?;
640 let agent_position = if let Some(pos) = pending_iter.position(|address| {
642 if let Ok(addr) = address {
643 &addr == account_id
644 } else {
645 false
646 }
647 }) {
648 pos
649 } else {
650 if active.contains(account_id) {
652 return Ok(AgentStatus::Active);
653 } else {
654 return Err(ContractError::AgentNotRegistered {});
655 }
656 };
657
658 if active.is_empty() && agent_position == 0 {
660 return Ok(AgentStatus::Nominated);
661 };
662
663 let max_agent_index =
666 max_agent_nomination_index(&c, env, AGENT_NOMINATION_STATUS.load(storage)?)?;
667 let agent_status = match max_agent_index {
668 Some(max_idx) if agent_position as u64 <= max_idx => AgentStatus::Nominated,
669 _ => AgentStatus::Pending,
670 };
671 Ok(agent_status)
672}
673
674fn max_agent_nomination_index(
676 cfg: &Config,
677 env: Env,
678 agent_nomination_status: AgentNominationStatus,
679) -> StdResult<Option<u64>> {
680 let block_height = env.block.height;
681
682 let agents_by_tasks_created = agent_nomination_status
683 .tasks_created_from_last_nomination
684 .saturating_div(cfg.min_tasks_per_agent);
685 let agents_by_height = agent_nomination_status
686 .start_height_of_nomination
687 .map_or(0, |start_height| {
688 (block_height - start_height) / cfg.agent_nomination_block_duration as u64
689 });
690 let agents_to_pass = min(agents_by_tasks_created, agents_by_height);
691 if agents_to_pass == 0 {
692 Ok(None)
693 } else {
694 Ok(Some(agents_to_pass - 1))
695 }
696}
697
698pub fn execute_tick(deps: DepsMut, env: Env) -> Result<Response, ContractError> {
699 let block_height = env.block.height;
700 let config = CONFIG.load(deps.storage)?;
701 let mut attributes = vec![];
702 let mut submessages = vec![];
703 let agents_active = AGENTS_ACTIVE.load(deps.storage)?;
704 let total_remove_agents: usize = agents_active.len();
705 let mut total_removed = 0;
706
707 for agent_id in agents_active {
708 let skip = (config.min_active_agent_count as usize) >= total_remove_agents - total_removed;
709 if !skip {
710 let stats = AGENT_STATS
711 .load(deps.storage, &agent_id)
712 .unwrap_or_default();
713 if block_height > stats.last_executed_slot + config.agents_eject_threshold {
714 let resp = unregister_agent(deps.storage, &deps.querier, &agent_id, None)
715 .unwrap_or_default();
716 attributes.extend_from_slice(&resp.attributes);
718 submessages.extend_from_slice(&resp.messages);
719 total_removed += 1;
720 }
721 }
722 }
723
724 if AGENTS_ACTIVE.load(deps.storage)?.is_empty() && AGENTS_PENDING.is_empty(deps.storage)? {
726 attributes.push(Attribute::new("lifecycle", "tick_failure"))
727 }
728 let response = Response::new()
729 .add_attribute("action", "tick")
730 .add_attributes(attributes)
731 .add_submessages(submessages);
732 Ok(response)
733}
734
735pub fn execute_pause(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
736 if PAUSED.load(deps.storage)? {
737 return Err(ContractError::ContractPaused);
738 }
739 let config = CONFIG.load(deps.storage)?;
740 if info.sender != config.pause_admin {
741 return Err(ContractError::Unauthorized);
742 }
743 PAUSED.save(deps.storage, &true)?;
744 Ok(Response::new().add_attribute("action", "pause_contract"))
745}
746
747pub fn execute_unpause(deps: DepsMut, info: MessageInfo) -> Result<Response, ContractError> {
748 if !PAUSED.load(deps.storage)? {
749 return Err(ContractError::ContractUnpaused);
750 }
751 let config = CONFIG.load(deps.storage)?;
752 if info.sender != config.owner_addr {
753 return Err(ContractError::Unauthorized);
754 }
755 PAUSED.save(deps.storage, &false)?;
756 Ok(Response::new().add_attribute("action", "unpause_contract"))
757}
758
759pub fn execute_add_agent_to_whitelist(
760 _env: Env,
761 deps: DepsMut,
762 info: MessageInfo,
763 agent_address: String,
764) -> Result<Response, ContractError> {
765 let config = CONFIG.may_load(deps.storage)?.unwrap();
766 if config.owner_addr != info.sender {
768 return Err(ContractError::Unauthorized);
769 }
770
771 let validated_agent_address = deps.api.addr_validate(agent_address.as_str())?;
772 APPROVED_AGENTS.save(deps.storage, &validated_agent_address, &Empty {})?;
773
774 Ok(Response::new().add_attribute("action", "add_agent_to_whitelist"))
775}
776
777pub fn execute_remove_agent_from_whitelist(
778 _env: Env,
779 deps: DepsMut,
780 info: MessageInfo,
781 agent_address: String,
782) -> Result<Response, ContractError> {
783 let config = CONFIG.may_load(deps.storage)?.unwrap();
784 if config.owner_addr != info.sender {
786 return Err(ContractError::Unauthorized);
787 }
788
789 let validated_agent_address = deps.api.addr_validate(agent_address.as_str())?;
790 APPROVED_AGENTS.remove(deps.storage, &validated_agent_address);
791
792 Ok(Response::new().add_attribute("action", "remove_agent_to_whitelist"))
793}
794
795fn on_task_created(
796 env: Env,
797 deps: DepsMut,
798 info: MessageInfo,
799 _: AgentOnTaskCreated,
800) -> Result<Response, ContractError> {
801 let config = CONFIG.may_load(deps.storage)?.unwrap();
802 croncat_tasks_contract::assert_caller_is_tasks_contract(&deps.querier, &config, &info.sender)?;
803
804 AGENT_NOMINATION_STATUS.update(deps.storage, |mut status| -> StdResult<_> {
805 if status.start_height_of_nomination.is_none() {
806 status.start_height_of_nomination = Some(env.block.height)
807 }
808 Ok(AgentNominationStatus {
809 start_height_of_nomination: status.start_height_of_nomination,
810 tasks_created_from_last_nomination: status.tasks_created_from_last_nomination + 1,
811 })
812 })?;
813
814 let response = Response::new().add_attribute("action", "on_task_created");
815 Ok(response)
816}
817
818fn on_task_completed(
819 deps: DepsMut,
820 info: MessageInfo,
821 args: AgentOnTaskCompleted,
822) -> Result<Response, ContractError> {
823 let config = CONFIG.may_load(deps.storage)?.unwrap();
824
825 croncat_manager_contract::assert_caller_is_manager_contract(
826 &deps.querier,
827 &config,
828 &info.sender,
829 )?;
830 let mut stats = AGENT_STATS.load(deps.storage, &args.agent_id)?;
831
832 if args.is_block_slot_task {
833 stats.completed_block_tasks += 1;
834 } else {
835 stats.completed_cron_tasks += 1;
836 }
837 AGENT_STATS.save(deps.storage, &args.agent_id, &stats)?;
838
839 let response = Response::new().add_attribute("action", "on_task_completed");
840 Ok(response)
841}
842
843fn validate_non_zero(num: u64, field_name: &str) -> Result<(), ContractError> {
845 if num == 0u64 {
846 Err(InvalidConfigurationValue {
847 field: field_name.to_string(),
848 })
849 } else {
850 Ok(())
851 }
852}
853
854fn validate_config_non_zero_u16(
857 opt_num: Option<u16>,
858 field_name: &str,
859) -> Result<(), ContractError> {
860 if let Some(num) = opt_num {
861 validate_non_zero(num as u64, field_name)
862 } else {
863 Ok(())
864 }
865}
866
867fn validate_config_non_zero_u64(
868 opt_num: Option<u64>,
869 field_name: &str,
870) -> Result<(), ContractError> {
871 if let Some(num) = opt_num {
872 validate_non_zero(num, field_name)
873 } else {
874 Ok(())
875 }
876}
877
878pub fn map_validate(deps: &DepsMut, agents: &[String]) -> StdResult<Vec<Addr>> {
881 agents
882 .iter()
883 .map(|addr| deps.api.addr_validate(addr.as_str()))
884 .collect()
885}