Skip to content

Commit 375f29a

Browse files
committed
feat: add anchor based example contract
1 parent 24cc52b commit 375f29a

File tree

1 file changed

+148
-0
lines changed
  • lazer/solana-anchor/programs/solana-anchor/src

1 file changed

+148
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
use anchor_lang::prelude::*;
2+
use pyth_lazer_solana_contract::protocol::{
3+
message::LeEcdsaMessage,
4+
payload::{PayloadData, PayloadPropertyValue},
5+
router::channel_ids::FIXED_RATE_200,
6+
};
7+
8+
declare_id!("My11111111111111111111111111111111111111111");
9+
10+
#[program]
11+
pub mod solana_anchor {
12+
use super::*;
13+
14+
pub fn initialize(ctx: Context<Initialize>, price_feed_id: u32) -> Result<()> {
15+
let state = &mut ctx.accounts.state;
16+
state.price_feed_id = price_feed_id;
17+
state.latest_timestamp = 0;
18+
state.latest_price = 0;
19+
Ok(())
20+
}
21+
22+
pub fn update_ecdsa(ctx: Context<UpdateEcdsa>, pyth_message: Vec<u8>) -> Result<()> {
23+
// Verify ECDSA signature
24+
let cpi_accounts = pyth_lazer_solana_contract::cpi::accounts::VerifyEcdsaMessage {
25+
payer: ctx.accounts.payer.to_account_info(),
26+
storage: ctx.accounts.pyth_storage.to_account_info(),
27+
treasury: ctx.accounts.pyth_treasury.to_account_info(),
28+
system_program: ctx.accounts.system_program.to_account_info(),
29+
};
30+
31+
let cpi_ctx = CpiContext::new(
32+
ctx.accounts.pyth_program.clone(),
33+
cpi_accounts,
34+
);
35+
36+
pyth_lazer_solana_contract::cpi::verify_ecdsa_message(cpi_ctx, pyth_message.clone())?;
37+
38+
// Deserialize and process the message
39+
let pyth_message = LeEcdsaMessage::deserialize_slice(&pyth_message)
40+
.map_err(|_| ErrorCode::InvalidMessage)?;
41+
42+
let data = PayloadData::deserialize_slice_le(&pyth_message.payload)
43+
.map_err(|_| ErrorCode::InvalidPayload)?;
44+
45+
apply_update(&mut ctx.accounts.state, &data)?;
46+
47+
Ok(())
48+
}
49+
}
50+
51+
#[derive(Accounts)]
52+
pub struct Initialize<'info> {
53+
#[account(mut)]
54+
pub payer: Signer<'info>,
55+
56+
#[account(
57+
init,
58+
payer = payer,
59+
space = 8 + std::mem::size_of::<State>(),
60+
seeds = [b"data"],
61+
bump
62+
)]
63+
pub state: Account<'info, State>,
64+
65+
pub system_program: Program<'info, System>,
66+
}
67+
68+
#[derive(Accounts)]
69+
pub struct UpdateEcdsa<'info> {
70+
#[account(mut)]
71+
pub payer: Signer<'info>,
72+
73+
#[account(mut, seeds = [b"data"], bump)]
74+
pub state: Account<'info, State>,
75+
76+
/// CHECK: This is the Pyth program
77+
#[account(address = pyth_lazer_solana_contract::ID)]
78+
pub pyth_program: AccountInfo<'info>,
79+
80+
#[account(address = pyth_lazer_solana_contract::STORAGE_ID)]
81+
pub pyth_storage: Account<'info, pyth_lazer_solana_contract::Storage>,
82+
83+
/// CHECK: This is the Pyth treasury account
84+
#[account(address = pyth_storage.treasury)]
85+
pub pyth_treasury: AccountInfo<'info>,
86+
87+
pub system_program: Program<'info, System>,
88+
}
89+
90+
#[account]
91+
pub struct State {
92+
pub price_feed_id: u32,
93+
pub latest_timestamp: u64,
94+
pub latest_price: i64,
95+
}
96+
97+
#[error_code]
98+
pub enum ErrorCode {
99+
#[msg("Invalid message")]
100+
InvalidMessage,
101+
#[msg("Invalid channel")]
102+
InvalidChannel,
103+
#[msg("Invalid payload")]
104+
InvalidPayload,
105+
#[msg("Invalid payload feed id")]
106+
InvalidPayloadFeedId,
107+
#[msg("Invalid payload property")]
108+
InvalidPayloadProperty,
109+
#[msg("Invalid payload timestamp")]
110+
InvalidPayloadTimestamp,
111+
}
112+
113+
fn apply_update(state: &mut Account<State>, data: &PayloadData) -> Result<()> {
114+
// Check the channel is what we expect
115+
if data.channel_id != FIXED_RATE_200 {
116+
return Err(ErrorCode::InvalidChannel.into());
117+
}
118+
119+
// Check if the timestamp is greater than the current timestamp
120+
if data.timestamp_us.0 <= state.latest_timestamp {
121+
return Err(ErrorCode::InvalidPayloadTimestamp.into());
122+
}
123+
124+
// Check the payload has a single feed
125+
if data.feeds.len() != 1 {
126+
return Err(ErrorCode::InvalidPayload.into());
127+
}
128+
129+
// Check the feed id is what we expect
130+
if data.feeds[0].feed_id.0 != state.price_feed_id {
131+
return Err(ErrorCode::InvalidPayloadFeedId.into());
132+
}
133+
134+
// Check the payload has a single price property
135+
if data.feeds[0].properties.len() != 1 {
136+
return Err(ErrorCode::InvalidPayloadProperty.into());
137+
}
138+
139+
// Check the price property is a price
140+
let PayloadPropertyValue::Price(Some(price)) = data.feeds[0].properties[0] else {
141+
return Err(ErrorCode::InvalidPayloadProperty.into());
142+
};
143+
144+
state.latest_price = price.into_inner().into();
145+
state.latest_timestamp = data.timestamp_us.0;
146+
147+
Ok(())
148+
}

0 commit comments

Comments
 (0)