gmsol_store/instructions/oracle/
custom.rs

1use anchor_lang::prelude::*;
2use gmsol_chainlink_datastreams::interface::ChainlinkDataStreamsInterface;
3use gmsol_utils::InitSpace;
4
5use crate::{
6    states::{AmountKey, PriceFeed, PriceFeedPrice, PriceProviderKind, Seed, Store},
7    utils::internal,
8    CoreError,
9};
10
11/// The accounts definition for [`initialize_price_feed`](crate::initialize_price_feed) instruction.
12#[derive(Accounts)]
13#[instruction(index: u16, provider: u8, token: Pubkey)]
14pub struct InitializePriceFeed<'info> {
15    /// Authority.
16    #[account(mut)]
17    pub authority: Signer<'info>,
18    /// Store.
19    pub store: AccountLoader<'info, Store>,
20    /// Price feed.
21    #[account(
22        init,
23        payer = authority,
24        space = 8 + PriceFeed::INIT_SPACE,
25        seeds = [
26            PriceFeed::SEED,
27            store.key().as_ref(),
28            authority.key().as_ref(),
29            &index.to_le_bytes(),
30            &[provider],
31            token.as_ref(),
32        ],
33        bump,
34    )]
35    pub price_feed: AccountLoader<'info, PriceFeed>,
36    /// The system program.
37    pub system_program: Program<'info, System>,
38}
39
40/// CHECK: only PRICE_KEEPER is allowed to initialize price feed.
41pub(crate) fn unchecked_initialize_price_feed(
42    ctx: Context<InitializePriceFeed>,
43    index: u16,
44    provider: PriceProviderKind,
45    token: &Pubkey,
46    feed_id: &Pubkey,
47) -> Result<()> {
48    require!(
49        matches!(provider, PriceProviderKind::ChainlinkDataStreams),
50        CoreError::NotSupportedCustomPriceProvider
51    );
52    let mut feed = ctx.accounts.price_feed.load_init()?;
53    feed.init(
54        ctx.bumps.price_feed,
55        index,
56        provider,
57        &ctx.accounts.store.key(),
58        &ctx.accounts.authority.key(),
59        token,
60        feed_id,
61    )?;
62    Ok(())
63}
64
65impl<'info> internal::Authentication<'info> for InitializePriceFeed<'info> {
66    fn authority(&self) -> &Signer<'info> {
67        &self.authority
68    }
69
70    fn store(&self) -> &AccountLoader<'info, Store> {
71        &self.store
72    }
73}
74
75/// The accounts definition for [`update_price_feed_with_chainlink`](crate::update_price_feed_with_chainlink) instruction.
76#[derive(Accounts)]
77pub struct UpdatePriceFeedWithChainlink<'info> {
78    /// Authority.
79    pub authority: Signer<'info>,
80    /// Store.
81    pub store: AccountLoader<'info, Store>,
82    /// Verifier Account.
83    /// CHECK: checked by CPI.
84    pub verifier_account: UncheckedAccount<'info>,
85    /// Access Controller Account.
86    /// CHECK: check by CPI.
87    pub access_controller: UncheckedAccount<'info>,
88    /// Config Account.
89    /// CHECK: check by CPI.
90    pub config_account: UncheckedAccount<'info>,
91    /// Price Feed Account.
92    #[account(mut, has_one = store, has_one = authority)]
93    pub price_feed: AccountLoader<'info, PriceFeed>,
94    /// Chainlink Data Streams Program.
95    pub chainlink: Interface<'info, ChainlinkDataStreamsInterface>,
96}
97
98/// CHECK: only PRICE_KEEPER can update custom price feed.
99pub(crate) fn unchecked_update_price_feed_with_chainlink(
100    ctx: Context<UpdatePriceFeedWithChainlink>,
101    compressed_report: Vec<u8>,
102) -> Result<()> {
103    let accounts = ctx.accounts;
104
105    require_eq!(
106        accounts.price_feed.load()?.provider()?,
107        PriceProviderKind::ChainlinkDataStreams,
108        CoreError::InvalidArgument
109    );
110
111    let price = accounts.decode_and_validate_report(&compressed_report)?;
112    accounts.verify_report(compressed_report)?;
113
114    accounts.price_feed.load_mut()?.update(
115        &price,
116        *accounts
117            .store
118            .load()?
119            .get_amount_by_key(AmountKey::OracleMaxFutureTimestampExcess)
120            .ok_or_else(|| error!(CoreError::Unimplemented))?,
121    )?;
122
123    Ok(())
124}
125
126impl<'info> internal::Authentication<'info> for UpdatePriceFeedWithChainlink<'info> {
127    fn authority(&self) -> &Signer<'info> {
128        &self.authority
129    }
130
131    fn store(&self) -> &AccountLoader<'info, Store> {
132        &self.store
133    }
134}
135
136impl UpdatePriceFeedWithChainlink<'_> {
137    fn decode_and_validate_report(&self, compressed_full_report: &[u8]) -> Result<PriceFeedPrice> {
138        use gmsol_chainlink_datastreams::report::decode_compressed_full_report;
139
140        let report = decode_compressed_full_report(compressed_full_report).map_err(|err| {
141            msg!("[Decode Error] {}", err);
142            error!(CoreError::InvalidPriceReport)
143        })?;
144
145        require_keys_eq!(
146            Pubkey::new_from_array(report.feed_id.0),
147            self.price_feed.load()?.feed_id,
148            CoreError::InvalidPriceReport
149        );
150
151        PriceFeedPrice::from_chainlink_report(&report)
152    }
153
154    fn verify_report(&self, signed_report: Vec<u8>) -> Result<()> {
155        use gmsol_chainlink_datastreams::interface::{verify, VerifyContext};
156
157        let ctx = CpiContext::new(
158            self.chainlink.to_account_info(),
159            VerifyContext {
160                verifier_account: self.verifier_account.to_account_info(),
161                access_controller: self.access_controller.to_account_info(),
162                user: self.store.to_account_info(),
163                config_account: self.config_account.to_account_info(),
164            },
165        );
166
167        verify(
168            ctx.with_signer(&[&self.store.load()?.signer_seeds()]),
169            signed_report,
170        )?;
171
172        Ok(())
173    }
174}