antegen_thread_program/instructions/
thread_error.rs

1use crate::{errors::*, state::PaymentDetails, *};
2use anchor_lang::prelude::*;
3
4/// Accounts required by the `thread_error` instruction.
5#[derive(Accounts)]
6pub struct ThreadError<'info> {
7    /// The executor reporting the error
8    #[account(mut)]
9    pub executor: Signer<'info>,
10
11    /// The thread that failed to execute
12    #[account(
13        mut,
14        seeds = [
15            SEED_THREAD,
16            thread.authority.as_ref(),
17            thread.id.as_slice(),
18        ],
19        bump = thread.bump,
20        constraint = !thread.paused @ AntegenThreadError::ThreadPaused,
21    )]
22    pub thread: Box<Account<'info, Thread>>,
23
24    /// The config for calculating reimbursement
25    #[account(
26        seeds = [SEED_CONFIG],
27        bump = config.bump,
28    )]
29    pub config: Account<'info, ThreadConfig>,
30
31    /// The config admin (receives 0 fee but needed for payment distribution)
32    /// CHECK: Validated by config
33    #[account(
34        mut,
35        constraint = admin.key().eq(&config.admin) @ AntegenThreadError::InvalidConfigAdmin,
36    )]
37    pub admin: UncheckedAccount<'info>,
38
39    #[account(address = anchor_lang::system_program::ID)]
40    pub system_program: Program<'info, System>,
41}
42
43pub fn thread_error(
44    ctx: Context<ThreadError>,
45    error_code: u32,
46    error_message: String,
47) -> Result<()> {
48    let clock = Clock::get()?;
49    let thread = &mut ctx.accounts.thread;
50    let config = &ctx.accounts.config;
51    let executor = &ctx.accounts.executor;
52
53    // Track starting balance for verification
54    let executor_lamports_start = executor.lamports();
55
56    // VALIDATION: Only allow error if we were the last executor (or no one was)
57    // This prevents reporting errors for threads executed by others
58    let last_executor_is_default = thread.last_executor == Pubkey::default();
59    let we_were_last_executor = thread.last_executor == executor.key();
60
61    require!(
62        last_executor_is_default || we_were_last_executor,
63        AntegenThreadError::NotLastExecutor
64    );
65
66    // VALIDATION 1: No error already reported
67    require!(
68        thread.last_error_time.is_none(),
69        AntegenThreadError::ErrorAlreadyReported
70    );
71
72    // VALIDATION 2: Must be overdue beyond grace + decay period
73    // Use the trait method to calculate time since ready
74    let thread_pubkey = thread.key();
75    let time_since_ready =
76        thread.validate_trigger(&clock, ctx.remaining_accounts, &thread_pubkey)?;
77    let error_threshold = config.grace_period_seconds + config.fee_decay_seconds;
78
79    require!(
80        time_since_ready >= error_threshold,
81        AntegenThreadError::ThreadNotSufficientlyOverdue
82    );
83
84    // Calculate reimbursement (no commission on errors)
85    const ERROR_REIMBURSEMENT: u64 = 10_000;
86
87    let rent_sysvar = Rent::get()?;
88    let available_lamports = thread
89        .to_account_info()
90        .lamports()
91        .saturating_sub(rent_sysvar.minimum_balance(thread.to_account_info().data_len()));
92
93    let payments = PaymentDetails {
94        fee_payer_reimbursement: ERROR_REIMBURSEMENT.min(available_lamports),
95        executor_commission: 0,
96        core_team_fee: 0,
97    };
98
99    // Use the existing trait method to distribute payments
100    thread.distribute_payments(
101        &thread.to_account_info(),
102        &executor.to_account_info(),
103        &ctx.accounts.admin.to_account_info(),
104        &payments,
105    )?;
106
107    // Set error timestamp
108    thread.last_error_time = Some(clock.unix_timestamp);
109
110    // Log the error details
111    msg!(
112        "ANTEGEN_ERROR: thread={}, executor={}, code={}, message={}, overdue_by={}s",
113        thread.key(),
114        executor.key(),
115        error_code,
116        error_message,
117        time_since_ready
118    );
119
120    // Verify executor balance increased (reimbursement happened)
121    let balance_change = executor.lamports() as i64 - executor_lamports_start as i64;
122    require!(balance_change >= 0, AntegenThreadError::PaymentFailed);
123
124    Ok(())
125}