gmsol_store/utils/
cpi.rs

1use anchor_lang::{prelude::*, Bumps};
2
3use crate::{
4    cpi::accounts::{CheckRole, ClearAllPrices, SetPricesFromPriceFeed},
5    states::RoleKey,
6};
7
8/// With Store.
9pub trait WithStore<'info> {
10    /// Get data store program.
11    fn store_program(&self) -> AccountInfo<'info>;
12
13    /// Get data store.
14    fn store(&self) -> AccountInfo<'info>;
15}
16
17/// Accounts that can be used for authentication.
18pub trait CpiAuthentication<'info>: Bumps + Sized + WithStore<'info> {
19    /// Get the authority to check.
20    ///
21    /// ## Notes
22    /// - `authority` should be a signer.
23    fn authority(&self) -> AccountInfo<'info>;
24
25    /// Get the cpi context for checking role or admin permission.
26    fn check_role_ctx(&self) -> CpiContext<'_, '_, '_, 'info, CheckRole<'info>> {
27        CpiContext::new(
28            self.store_program(),
29            CheckRole {
30                authority: self.authority(),
31                store: self.store(),
32            },
33        )
34    }
35
36    /// Callback on authentication error.
37    fn on_error(&self) -> Result<()>;
38}
39
40/// Provides access control utils for [`CpiAuthentication`]s.
41pub trait CpiAuthenticate<'info>: CpiAuthentication<'info> {
42    /// Check that the `authority` has the given `role`.
43    fn only(ctx: &Context<Self>, role: &str) -> Result<()> {
44        let has_role =
45            crate::cpi::check_role(ctx.accounts.check_role_ctx(), role.to_string())?.get();
46        if has_role {
47            Ok(())
48        } else {
49            ctx.accounts.on_error()
50        }
51    }
52
53    /// Check that the `authority` is an admin.
54    fn only_admin(ctx: &Context<Self>) -> Result<()> {
55        let is_admin = crate::cpi::check_admin(ctx.accounts.check_role_ctx())?.get();
56        if is_admin {
57            Ok(())
58        } else {
59            ctx.accounts.on_error()
60        }
61    }
62
63    /// Check that the `authority` has the [`MARKET_KEEPER`](`RoleKey::MARKET_KEEPER`) role.
64    fn only_market_keeper(ctx: &Context<Self>) -> Result<()> {
65        Self::only(ctx, RoleKey::MARKET_KEEPER)
66    }
67
68    /// Check that the `authority` has the [`ORDER_KEEPER`](`RoleKey::ORDER_KEEPER`) role.
69    fn only_order_keeper(ctx: &Context<Self>) -> Result<()> {
70        Self::only(ctx, RoleKey::ORDER_KEEPER)
71    }
72}
73
74impl<'info, T> CpiAuthenticate<'info> for T where T: CpiAuthentication<'info> {}
75
76/// Accounts that with oracle context.
77pub trait WithOracle<'info>: WithStore<'info> {
78    /// Get the chainlink program.
79    fn chainlink_program(&self) -> Option<AccountInfo<'info>>;
80
81    /// Get the oracle account.
82    fn oracle(&self) -> AccountInfo<'info>;
83
84    /// Get the token map account.
85    fn token_map(&self) -> AccountInfo<'info>;
86
87    /// Get controller account.
88    fn controller(&self) -> AccountInfo<'info>;
89}
90
91/// Extension trait for [`WithOracle`].
92pub trait WithOracleExt<'info>: WithOracle<'info> {
93    /// Get the CPI context for set prices.
94    fn set_prices_from_price_feed_ctx(
95        &self,
96        feeds: Vec<AccountInfo<'info>>,
97    ) -> CpiContext<'_, '_, '_, 'info, SetPricesFromPriceFeed<'info>> {
98        CpiContext::new(
99            self.store_program().to_account_info(),
100            SetPricesFromPriceFeed {
101                authority: self.controller(),
102                store: self.store(),
103                token_map: self.token_map(),
104                oracle: self.oracle(),
105                chainlink_program: self.chainlink_program(),
106            },
107        )
108        .with_remaining_accounts(feeds)
109    }
110
111    /// Get the CPI context for clear all prices.
112    fn clear_all_prices_ctx(&self) -> CpiContext<'_, '_, '_, 'info, ClearAllPrices<'info>> {
113        CpiContext::new(
114            self.store_program().to_account_info(),
115            ClearAllPrices {
116                authority: self.controller(),
117                store: self.store(),
118                oracle: self.oracle(),
119            },
120        )
121    }
122
123    /// Run the given function inside the scope with oracle prices.
124    fn with_oracle_prices<T>(
125        &mut self,
126        tokens: Vec<Pubkey>,
127        remaining_accounts: &'info [AccountInfo<'info>],
128        signer_seeds: &[&[u8]],
129        f: impl FnOnce(&mut Self, &'info [AccountInfo<'info>]) -> Result<T>,
130    ) -> Result<T> {
131        require_gte!(
132            remaining_accounts.len(),
133            tokens.len(),
134            ErrorCode::AccountNotEnoughKeys
135        );
136        let feeds = remaining_accounts[..tokens.len()].to_vec();
137        let remaining_accounts = &remaining_accounts[tokens.len()..];
138        crate::cpi::set_prices_from_price_feed(
139            self.set_prices_from_price_feed_ctx(feeds)
140                .with_signer(&[signer_seeds]),
141            tokens,
142        )?;
143        let output = f(self, remaining_accounts)?;
144        crate::cpi::clear_all_prices(self.clear_all_prices_ctx().with_signer(&[signer_seeds]))?;
145        Ok(output)
146    }
147}
148
149impl<'info, T> WithOracleExt<'info> for T where T: WithOracle<'info> {}