avs_oracle/
lib.rs

1use anchor_lang::prelude::*;
2use restaking_programs::cpi::accounts::SlashOperator;
3use restaking_programs::program::RestakingPrograms;
4use restaking_programs::{OperatorAccount, OperatorVault, RewardTreasury};
5
6declare_id!("CmusrUV5ChdfHdTFqHuCHQW8hzqjoawd5YbDQ7km7BS7");
7
8#[program]
9pub mod avs_oracle {
10    use super::*;
11
12    pub fn create_task(
13        ctx: Context<CreateTask>,
14        task_id: u64,
15        submission_deadline_slots: u64,
16        verification_threshold_bps: u64,
17    ) -> Result<()> {
18        let task = &mut ctx.accounts.task_account;
19        let current_slot = Clock::get()?.slot;
20
21        task.avs = ctx.accounts.avs_owner.key();
22        task.task_id = task_id;
23        task.submission_deadline = current_slot + submission_deadline_slots;
24        task.verification_threshold_bps = verification_threshold_bps;
25        task.created_slot = current_slot;
26        task.total_submissions = 0;
27        task.verified_submissions = 0;
28        task.active = true;
29        task.bump = ctx.bumps.task_account;
30
31        emit!(TaskCreatedEvent {
32            task_id,
33            avs: task.avs,
34            deadline: task.submission_deadline,
35            threshold_bps: verification_threshold_bps,
36        });
37
38        msg!(
39            "TASK CREATED WITH VALUES: Task {} created by AVS {}",
40            task_id,
41            task.avs
42        );
43
44        Ok(())
45    }
46
47    pub fn submit_task_result(
48        ctx: Context<SubmitTaskResult>,
49        submitted_price: i64,
50        confidence: u64,
51        publish_time: i64,
52    ) -> Result<()> {
53        let task = &mut ctx.accounts.task_account;
54        let submission = &mut ctx.accounts.task_submission;
55        let operator_account = &ctx.accounts.operator_account;
56        let operator_avs_reg = &ctx.accounts.operator_avs_registration;
57
58        let current_slot = Clock::get()?.slot;
59        require!(
60            current_slot <= task.submission_deadline,
61            ErrorCode::TaskExpired
62        );
63
64        require!(task.active, ErrorCode::TaskNotActive);
65        require!(operator_account.active, ErrorCode::OperatorNotActive);
66        require!(operator_avs_reg.active, ErrorCode::OperatorNotOptedIn);
67        require!(
68            operator_account.bond_amount >= 1_000_000_000,
69            ErrorCode::InsufficientBond
70        );
71        require!(
72            operator_avs_reg.tasks_failed < 5,
73            ErrorCode::TooManyFailures
74        );
75
76        submission.task = task.key();
77        submission.operator = ctx.accounts.operator.key();
78        submission.submitted_price = submitted_price;
79        submission.confidence = confidence;
80        submission.publish_time = publish_time;
81        submission.submitted_slot = current_slot;
82        submission.verified = false;
83        submission.is_correct = false;
84        submission.bump = ctx.bumps.task_submission;
85
86        task.total_submissions = task.total_submissions.checked_add(1).unwrap();
87
88        emit!(TaskSubmittedEvent {
89            task_id: task.task_id,
90            operator: submission.operator,
91            submitted_price,
92            submitted_slot: current_slot
93        });
94
95        msg!(
96            "SUBMISSION OF TASK DONE: Operator {} submitted result for task {}",
97            submission.operator,
98            task.task_id
99        );
100
101        Ok(())
102    }
103
104    pub fn verify_and_slash_if_wrong(
105        ctx: Context<VerifyAndSlashIfWrong>,
106        operator_owner: Pubkey,
107        actual_price: i64,
108    ) -> Result<()> {
109        let task = &mut ctx.accounts.task_account;
110        let submission = &mut ctx.accounts.task_submission;
111
112        require!(!submission.verified, ErrorCode::AlreadyVerified);
113        
114        let sb_price = actual_price;
115        let sb_conf = 0u64;
116        let submitted_price = submission.submitted_price;
117
118        let diff = (sb_price - submitted_price).abs();
119        let threshold = (sb_price.abs() as u64)
120            .checked_mul(task.verification_threshold_bps)
121            .unwrap()
122            .checked_div(10000)
123            .unwrap() as i64;
124
125        let is_correct = diff <= threshold;
126
127        submission.verified = true;
128        submission.is_correct = is_correct;
129        submission.actual_pyth_price = sb_price;
130        submission.actual_pyth_conf = sb_conf;
131
132        task.verified_submissions = task.verified_submissions.checked_add(1).unwrap();
133
134        if !is_correct {
135            msg!("Operator wrong! Slashing via CPI...");
136
137            let operator_account = &ctx.accounts.operator_account;
138            let slash_amount = operator_account.bond_amount / 10;
139
140            let cpi_program = ctx.accounts.restaking_program.to_account_info();
141            let cpi_accounts = SlashOperator {
142                authority: ctx.accounts.avs_authority.to_account_info(),
143                operator_account: ctx.accounts.operator_account.to_account_info(),
144                vault: ctx.accounts.operator_vault.to_account_info(),
145                treasury: ctx.accounts.treasury.to_account_info(),
146                system_program: ctx.accounts.system_program.to_account_info(),
147            };
148
149            let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
150
151            restaking_programs::cpi::slash_operator(cpi_ctx, operator_owner, slash_amount)?;
152
153            msg!(
154                "Operator slashed {} lamports and rewards channeled to impact treasury",
155                slash_amount
156            );
157        } else {
158            msg!("Operator correct!");
159        }
160
161        emit!(SubmissionVerifiedEvent {
162            task_id: task.task_id,
163            operator: submission.operator,
164            is_correct,
165            submitted_price,
166            actual_price: sb_price,
167            difference: diff,
168        });
169
170        Ok(())
171    }
172
173    pub fn close_task(ctx: Context<CloseTask>) -> Result<()> {
174        let task = &mut ctx.accounts.task_account;
175
176        let current_slot = Clock::get()?.slot;
177        require!(
178            current_slot > task.submission_deadline,
179            ErrorCode::TaskStillActive
180        );
181
182        task.active = false;
183
184        msg!(
185            "Task {} closed. Submissions: {}, Verified: {}",
186            task.task_id,
187            task.total_submissions,
188            task.verified_submissions
189        );
190
191        Ok(())
192    }
193}
194
195#[derive(Accounts)]
196#[instruction(task_id: u64)]
197pub struct CreateTask<'info> {
198    #[account(mut)]
199    pub avs_owner: Signer<'info>,
200
201    #[account(
202        init,
203        payer = avs_owner,
204        space = 8 + TaskAccount::INIT_SPACE,
205        seeds = [b"task", avs_owner.key().as_ref(), task_id.to_le_bytes().as_ref()], 
206        bump
207    )]
208    pub task_account: Account<'info, TaskAccount>,
209
210    pub system_program: Program<'info, System>,
211}
212
213#[derive(Accounts)]
214pub struct SubmitTaskResult<'info> {
215    #[account(mut)]
216    pub operator: Signer<'info>,
217
218    #[account(mut)]
219    pub task_account: Account<'info, TaskAccount>,
220
221    #[account(
222        init,
223        payer = operator,
224        space = 8 + TaskSubmission::INIT_SPACE,
225        seeds = [b"submission", task_account.key().as_ref(), operator.key().as_ref()],  
226        bump
227    )]
228    pub task_submission: Account<'info, TaskSubmission>,
229
230    #[account(
231        seeds = [b"operator", operator.key().as_ref()],
232        bump = operator_account.bump,
233        seeds::program = restaking_program.key(),
234        constraint = operator_account.active @ ErrorCode::OperatorNotActive,
235        constraint = operator_account.owner == operator.key() @ ErrorCode::Unauthorized
236    )]
237    pub operator_account: Account<'info, restaking_programs::OperatorAccount>,
238
239    #[account(
240        seeds = [
241            b"operator_avs",
242            operator.key().as_ref(),
243            task_account.avs.as_ref()
244        ],
245        bump = operator_avs_registration.bump,
246        seeds::program = restaking_program.key(),
247        constraint = operator_avs_registration.active @ ErrorCode::OperatorNotOptedIn
248    )]
249    pub operator_avs_registration: Account<'info, restaking_programs::OperatorAvsRegistration>,
250
251    pub restaking_program: Program<'info, restaking_programs::program::RestakingPrograms>,
252    pub system_program: Program<'info, System>,
253}
254
255#[derive(Accounts)]
256#[instruction(operator_owner: Pubkey)]
257pub struct VerifyAndSlashIfWrong<'info> {
258    #[account(mut)]
259    pub avs_authority: Signer<'info>,
260
261    pub task_account: Account<'info, TaskAccount>,
262
263    #[account(
264        mut,
265        seeds = [b"submission", task_account.key().as_ref(), task_submission.operator.as_ref()],
266        bump = task_submission.bump
267    )]
268    pub task_submission: Account<'info, TaskSubmission>,
269
270    pub restaking_program: Program<'info, RestakingPrograms>,
271
272    #[account(
273        mut,
274        seeds = [b"operator", operator_owner.as_ref()],
275        bump = operator_account.bump,
276        seeds::program = restaking_program.key()
277    )]
278    pub operator_account: Account<'info, OperatorAccount>,
279
280    #[account(
281        mut,
282        seeds = [b"vault", operator_owner.as_ref()],
283        bump = operator_account.vault_bump,
284        seeds::program = restaking_program.key()
285    )]
286    pub operator_vault: Account<'info, OperatorVault>,
287
288    #[account(
289        mut,
290        seeds = [b"reward_treasury"],
291        bump = treasury.bump,
292        seeds::program = restaking_program.key()
293    )]
294    pub treasury: Account<'info, RewardTreasury>,
295
296    pub system_program: Program<'info, System>,
297}
298
299#[derive(Accounts)]
300pub struct CloseTask<'info> {
301    #[account(mut)]
302    pub avs_owner: Signer<'info>,
303
304    #[account(
305        mut,
306        constraint = task_account.avs == avs_owner.key() @ ErrorCode::Unauthorized
307    )]
308    pub task_account: Account<'info, TaskAccount>,
309}
310
311#[account]
312#[derive(InitSpace, Debug)]
313pub struct TaskAccount {
314    pub avs: Pubkey,
315    pub task_id: u64,
316    pub submission_deadline: u64,
317    pub verification_threshold_bps: u64,
318    pub created_slot: u64,
319    pub total_submissions: u32,
320    pub verified_submissions: u32,
321    pub active: bool,
322    pub bump: u8,
323}
324
325#[account]
326#[derive(InitSpace, Debug)]
327pub struct TaskSubmission {
328    pub task: Pubkey,
329    pub operator: Pubkey,
330    pub submitted_price: i64,
331    pub confidence: u64,
332    pub publish_time: i64,
333    pub submitted_slot: u64,
334    pub verified: bool,
335    pub is_correct: bool,
336    pub actual_pyth_price: i64,
337    pub actual_pyth_conf: u64,
338    pub bump: u8,
339}
340
341#[event]
342pub struct TaskCreatedEvent {
343    pub task_id: u64,
344    pub avs: Pubkey,
345    pub deadline: u64,
346    pub threshold_bps: u64,
347}
348
349#[event]
350pub struct TaskSubmittedEvent {
351    pub task_id: u64,
352    pub operator: Pubkey,
353    pub submitted_price: i64,
354    pub submitted_slot: u64,
355}
356
357#[event]
358pub struct SubmissionVerifiedEvent {
359    pub task_id: u64,
360    pub operator: Pubkey,
361    pub is_correct: bool,
362    pub submitted_price: i64,
363    pub actual_price: i64,
364    pub difference: i64,
365}
366
367#[error_code]
368pub enum ErrorCode {
369    #[msg("Task submission deadline has passed")]
370    TaskExpired,
371
372    #[msg("Task is not active")]
373    TaskNotActive,
374
375    #[msg("Submission already verified")]
376    AlreadyVerified,
377
378    #[msg("Task is still active, cannot close yet")]
379    TaskStillActive,
380
381    #[msg("Unauthorized action")]
382    Unauthorized,
383
384    #[msg("Invalid Pyth price data")]
385    InvalidPythPrice,
386
387    #[msg("Operator is not active")]
388    OperatorNotActive,
389
390    #[msg("Operator has not opted into this AVS")]
391    OperatorNotOptedIn,
392
393    #[msg("Operator has insufficient bond")]
394    InsufficientBond,
395
396    #[msg("Operator has too many failed tasks")]
397    TooManyFailures,
398}