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}