gmsol/utils/builder/
oracle.rs

1use std::{collections::HashMap, future::Future, ops::Deref};
2
3use anchor_client::solana_sdk::{pubkey::Pubkey, signer::Signer};
4use gmsol_solana_utils::{
5    bundle_builder::{BundleBuilder, BundleOptions},
6    transaction_builder::TransactionBuilder,
7};
8use gmsol_store::states::{common::TokensWithFeed, PriceProviderKind};
9use time::OffsetDateTime;
10
11use super::{MakeBundleBuilder, SetExecutionFee};
12
13/// A mapping from feed id to the corresponding feed address.
14pub type FeedAddressMap = std::collections::HashMap<Pubkey, Pubkey>;
15
16/// Build with pull oracle instructions.
17pub struct WithPullOracle<O, T>
18where
19    O: PullOracle,
20{
21    builder: T,
22    pull_oracle: O,
23    price_updates: O::PriceUpdates,
24}
25
26impl<O: PullOracle, T> WithPullOracle<O, T> {
27    /// Construct transactions with the given pull oracle and price updates.
28    pub fn with_price_updates(pull_oracle: O, builder: T, price_updates: O::PriceUpdates) -> Self {
29        Self {
30            builder,
31            pull_oracle,
32            price_updates,
33        }
34    }
35
36    /// Fetch the required price updates and use them to construct transactions.
37    pub async fn from_consumer(
38        pull_oracle: O,
39        mut builder: T,
40        after: Option<OffsetDateTime>,
41    ) -> crate::Result<(Self, FeedIds)>
42    where
43        T: PullOraclePriceConsumer,
44    {
45        let feed_ids = builder.feed_ids().await?;
46        let price_updates = pull_oracle.fetch_price_updates(&feed_ids, after).await?;
47
48        Ok((
49            Self::with_price_updates(pull_oracle, builder, price_updates),
50            feed_ids,
51        ))
52    }
53
54    /// Fetch the required price updates and use them to construct transactions.
55    pub async fn new(
56        pull_oracle: O,
57        builder: T,
58        after: Option<OffsetDateTime>,
59    ) -> crate::Result<Self>
60    where
61        T: PullOraclePriceConsumer,
62    {
63        Ok(Self::from_consumer(pull_oracle, builder, after).await?.0)
64    }
65}
66
67impl<'a, C: Deref<Target = impl Signer> + Clone, O, T> MakeBundleBuilder<'a, C>
68    for WithPullOracle<O, T>
69where
70    O: PostPullOraclePrices<'a, C>,
71    T: PullOraclePriceConsumer + MakeBundleBuilder<'a, C>,
72{
73    async fn build_with_options(
74        &mut self,
75        options: BundleOptions,
76    ) -> gmsol_solana_utils::Result<BundleBuilder<'a, C>> {
77        let (instructions, map) = self
78            .pull_oracle
79            .fetch_price_update_instructions(&self.price_updates, options.clone())
80            .await
81            .map_err(gmsol_solana_utils::Error::custom)?;
82
83        for (kind, map) in map {
84            self.builder
85                .process_feeds(kind, map)
86                .map_err(gmsol_solana_utils::Error::custom)?;
87        }
88
89        let consume = self.builder.build_with_options(options).await?;
90
91        let PriceUpdateInstructions {
92            post: mut tx,
93            close,
94        } = instructions;
95
96        tx.append(consume, false)?;
97        tx.append(close, true)?;
98
99        Ok(tx)
100    }
101}
102
103impl<O: PullOracle, T: SetExecutionFee> SetExecutionFee for WithPullOracle<O, T> {
104    fn set_execution_fee(&mut self, lamports: u64) -> &mut Self {
105        self.builder.set_execution_fee(lamports);
106        self
107    }
108}
109
110impl<O: PullOracle, T: PullOraclePriceConsumer> PullOraclePriceConsumer for WithPullOracle<O, T> {
111    fn feed_ids(&mut self) -> impl Future<Output = crate::Result<FeedIds>> {
112        self.builder.feed_ids()
113    }
114
115    fn process_feeds(
116        &mut self,
117        provider: PriceProviderKind,
118        map: FeedAddressMap,
119    ) -> crate::Result<()> {
120        self.builder.process_feeds(provider, map)
121    }
122}
123
124/// Feed IDs.
125#[derive(Debug, Clone)]
126pub struct FeedIds {
127    store: Pubkey,
128    tokens_with_feed: TokensWithFeed,
129}
130
131impl FeedIds {
132    /// Get the store address.
133    pub fn store(&self) -> &Pubkey {
134        &self.store
135    }
136
137    /// Create from `store` and `tokens_with_feed`.
138    pub fn new(store: Pubkey, tokens_with_feed: TokensWithFeed) -> Self {
139        Self {
140            store,
141            tokens_with_feed,
142        }
143    }
144}
145
146impl Deref for FeedIds {
147    type Target = TokensWithFeed;
148
149    fn deref(&self) -> &Self::Target {
150        &self.tokens_with_feed
151    }
152}
153
154/// Pull Oracle.
155pub trait PullOracle {
156    /// Price Updates.
157    type PriceUpdates;
158
159    /// Fetch Price Update.
160    fn fetch_price_updates(
161        &self,
162        feed_ids: &FeedIds,
163        after: Option<OffsetDateTime>,
164    ) -> impl Future<Output = crate::Result<Self::PriceUpdates>>;
165}
166
167/// Price Update Instructions.
168pub struct PriceUpdateInstructions<'a, C> {
169    post: BundleBuilder<'a, C>,
170    close: BundleBuilder<'a, C>,
171}
172
173impl<'a, C: Deref<Target = impl Signer> + Clone> PriceUpdateInstructions<'a, C> {
174    /// Create a new empty price update instructions.
175    pub fn new(client: &'a crate::Client<C>, options: BundleOptions) -> Self {
176        Self {
177            post: client.bundle_with_options(options.clone()),
178            close: client.bundle_with_options(options),
179        }
180    }
181}
182
183impl<'a, C: Deref<Target = impl Signer> + Clone> PriceUpdateInstructions<'a, C> {
184    /// Push a post price update instruction.
185    #[allow(clippy::result_large_err)]
186    pub fn try_push_post(
187        &mut self,
188        instruction: TransactionBuilder<'a, C>,
189    ) -> Result<(), (TransactionBuilder<'a, C>, gmsol_solana_utils::Error)> {
190        self.post.try_push(instruction)?;
191        Ok(())
192    }
193
194    /// Push a close instruction.
195    #[allow(clippy::result_large_err)]
196    pub fn try_push_close(
197        &mut self,
198        instruction: TransactionBuilder<'a, C>,
199    ) -> Result<(), (TransactionBuilder<'a, C>, gmsol_solana_utils::Error)> {
200        self.close.try_push(instruction)?;
201        Ok(())
202    }
203
204    /// Merge.
205    pub fn merge(&mut self, other: Self) -> gmsol_solana_utils::Result<()> {
206        self.post.append(other.post, false)?;
207        self.close.append(other.close, false)?;
208        Ok(())
209    }
210}
211
212/// Post pull oracle price updates.
213pub trait PostPullOraclePrices<'a, C>: PullOracle {
214    /// Fetch instructions to post the price updates.
215    fn fetch_price_update_instructions(
216        &self,
217        price_updates: &Self::PriceUpdates,
218        options: BundleOptions,
219    ) -> impl Future<
220        Output = crate::Result<(
221            PriceUpdateInstructions<'a, C>,
222            HashMap<PriceProviderKind, FeedAddressMap>,
223        )>,
224    >;
225}
226
227/// Pull Oracle Price Consumer.
228pub trait PullOraclePriceConsumer {
229    /// Returns a reference to tokens and their associated feed IDs that require price updates.
230    fn feed_ids(&mut self) -> impl Future<Output = crate::Result<FeedIds>>;
231
232    /// Processes the feed address map returned from the pull oracle.
233    fn process_feeds(
234        &mut self,
235        provider: PriceProviderKind,
236        map: FeedAddressMap,
237    ) -> crate::Result<()>;
238}
239
240impl<T: PullOraclePriceConsumer> PullOraclePriceConsumer for &mut T {
241    fn feed_ids(&mut self) -> impl Future<Output = crate::Result<FeedIds>> {
242        (**self).feed_ids()
243    }
244
245    fn process_feeds(
246        &mut self,
247        provider: PriceProviderKind,
248        map: FeedAddressMap,
249    ) -> crate::Result<()> {
250        (**self).process_feeds(provider, map)
251    }
252}