solana_randomness_service_lite/
lib.rs

1#![cfg_attr(doc_cfg, feature(doc_cfg))]
2
3//!  The Solana Randomness Service uses a Switchboard SGX enabled oracle to provide randomness to any Solana program using a callback instruction.
4//!
5//!  **Program ID:** `RANDMo5gFnqnXJW5Z52KNmd24sAo95KAd5VbiCtq5Rh`
6//!
7//! See the crate `solana-randomness-service` for the full CPI interface.
8//!
9//!  # Example Program
10//!
11//! ```
12//! use anchor_lang::prelude::*;
13//! use solana_randomness_service_lite::{SimpleRandomnessV1Request, ID as SolanaRandomnessServiceID};
14//!
15//! #[program]
16//! pub mod solana_randomness_consumer {
17//!     use super::*;
18//!
19//!     pub fn request_randomness(ctx: Context<RequestRandomness>) -> anchor_lang::prelude::Result<()> {
20//!         msg!("Requesting randomness...");
21//!
22//!         let request = SimpleRandomnessV1Request {
23//!             request: ctx.accounts.randomness_request.to_account_info(),
24//!             escrow: ctx.accounts.randomness_escrow.to_account_info(),
25//!             state: ctx.accounts.randomness_state.to_account_info(),
26//!             mint: ctx.accounts.randomness_mint.to_account_info(),
27//!             payer: ctx.accounts.payer.to_account_info(),
28//!             system_program: ctx.accounts.system_program.to_account_info(),
29//!             token_program: ctx.accounts.token_program.to_account_info(),
30//!             associated_token_program: ctx.accounts.associated_token_program.to_account_info(),
31//!         };
32//!         request.invoke(
33//!             ctx.accounts.randomness_service.to_account_info(),
34//!             8, // Request 8 bytes of randomness
35//!             &solana_randomness_service_lite::Callback::new(
36//!                 ID,
37//!                 vec![
38//!                     AccountMeta::new_readonly(ctx.accounts.randomness_state.key(), true).into(),
39//!                     AccountMeta::new_readonly(ctx.accounts.randomness_request.key(), false).into(),
40//!                 ],
41//!                 [190, 217, 49, 162, 99, 26, 73, 234].to_vec(), // Our callback ixn discriminator. The oracle will append the randomness bytes to the end
42//!             ),
43//!             &Some(solana_randomness_service_lite::TransactionOptions {
44//!                 compute_units: Some(1_000_000),
45//!                 compute_unit_price: Some(100),
46//!             }),
47//!         )?;
48//!
49//!         // Here we can emit some event to index our requests
50//!
51//!         Ok(())
52//!     }
53//! }
54//!
55//! #[derive(Accounts)]
56//! pub struct RequestRandomness<'info> {
57//!     /// CHECK: manually check programID and executable status
58//!     #[account(
59//!         constraint = randomness_service.key() == SolanaRandomnessServiceID,
60//!         constraint = randomness_service.executable,
61//!     )]
62//!     pub randomness_service: AccountInfo<'info>,
63//!
64//!     /// The account that will be created on-chain to hold the randomness request.
65//!     /// Used by the off-chain oracle to pickup the request and fulfill it.
66//!     /// CHECK: todo
67//!     #[account(
68//!         mut,
69//!         signer,
70//!         owner = system_program.key(),
71//!         constraint = randomness_request.data_len() == 0 && randomness_request.lamports() == 0,
72//!     )]
73//!     pub randomness_request: AccountInfo<'info>,
74//!
75//!     /// The TokenAccount that will store the funds for the randomness request.
76//!     /// CHECK: todo
77//!     #[account(
78//!         mut,
79//!         owner = system_program.key(),
80//!         constraint = randomness_escrow.data_len() == 0 && randomness_escrow.lamports() == 0,
81//!     )]
82//!     pub randomness_escrow: AccountInfo<'info>,
83//!
84//!     /// The randomness service's state account. Responsible for storing the
85//!     /// reward escrow and the cost per random byte.
86//!     #[account(
87//!         seeds = [b"STATE"],
88//!         bump = randomness_state.bump,
89//!         seeds::program = randomness_service.key(),
90//!     )]
91//!     pub randomness_state: Box<Account<'info, solana_randomness_service::State>>,
92//!
93//!     /// The token mint to use for paying for randomness requests.
94//!     #[account(address = NativeMint::ID)]
95//!     pub randomness_mint: Account<'info, Mint>,
96//!
97//!     /// The account that will pay for the randomness request.
98//!     #[account(mut)]
99//!     pub payer: Signer<'info>,
100//!
101//!     /// The Solana System program. Used to allocate space on-chain for the randomness_request account.
102//!     pub system_program: Program<'info, System>,
103//!
104//!     /// The Solana Token program. Used to transfer funds to the randomness escrow.
105//!     pub token_program: Program<'info, Token>,
106//!
107//!     /// The Solana Associated Token program. Used to create the TokenAccount for the randomness escrow.
108//!     pub associated_token_program: Program<'info, AssociatedToken>,
109//! }
110//! ```
111use borsh::{BorshDeserialize, BorshSerialize};
112pub use solana_program::account_info::AccountInfo;
113pub use solana_program::instruction::AccountMeta;
114pub use solana_program::program_error::ProgramError;
115pub use solana_program::pubkey::Pubkey;
116use solana_program::{declare_id, instruction::Instruction};
117use solana_program::{program::invoke, program::invoke_signed, pubkey};
118
119pub mod types;
120pub use types::*;
121
122declare_id!("RANDMo5gFnqnXJW5Z52KNmd24sAo95KAd5VbiCtq5Rh");
123
124pub const SWITCHBOARD_PROGRAM_ID: Pubkey = pubkey!("sbattyXrzedoNATfc4L31wC9Mhxsi1BmFhTiN8gDshx");
125
126pub const RANDOMNESS_SERVICE_STATE: Pubkey =
127    pubkey!("889J3BcnDDBMA651BoZNnuKrhQtXkLRzDuKhnJkWUfKA");
128pub const RANDOMNESS_SERVICE_REWARD_WALLET: Pubkey =
129    pubkey!("3X7Jy3dc7eRSP9ECWvxiiQWG8gfwmYq1zENfWpzygt6D");
130pub const RANDOMNESS_SERVICE_REWARD_MINT: Pubkey =
131    pubkey!("So11111111111111111111111111111111111111112");
132
133pub const MAINNET_SWITCHBOARD_FUNCTION: Pubkey =
134    pubkey!("yxvdQ9D6eovAQqacSyAL9vYhXXtdtnmgCABfaz8cg2W");
135pub const MAINNET_SWITCHBOARD_SERVICE: Pubkey =
136    pubkey!("3gGs95XHv47gY6aUvSPhQmVrWYt3M6Lz3nxE9eMS3bot");
137
138pub const DEVNET_SWITCHBOARD_FUNCTION: Pubkey =
139    pubkey!("AHV7ygefHZQ5extiZ4GbseGANg3AwBWgSUfnUktTrxjd");
140pub const DEVNET_SWITCHBOARD_SERVICE: Pubkey =
141    pubkey!("2fpdEbugwThMjRQ728Ne4zwGsrjFcCtmYDnwGtzScfnL");
142
143///
144pub struct SimpleRandomnessV1Request<'info> {
145    pub request: AccountInfo<'info>,
146    pub escrow: AccountInfo<'info>,
147    pub state: AccountInfo<'info>,
148    pub mint: AccountInfo<'info>,
149    pub payer: AccountInfo<'info>,
150    pub system_program: AccountInfo<'info>,
151    pub token_program: AccountInfo<'info>,
152    pub associated_token_program: AccountInfo<'info>,
153}
154
155impl<'info> SimpleRandomnessV1Request<'info> {
156    pub const DISCRIMINATOR: [u8; 8] = [179, 249, 152, 75, 164, 218, 230, 36];
157
158    pub fn discriminator() -> [u8; 8] {
159        Self::DISCRIMINATOR
160    }
161
162    pub fn get_instruction(
163        &self,
164        program_id: Pubkey,
165        num_bytes: u8,
166        callback: &Callback,
167        options: &Option<TransactionOptions>,
168    ) -> Result<Instruction, ProgramError> {
169        let accounts = self.to_account_metas();
170
171        let mut data: Vec<u8> = Self::discriminator().to_vec();
172        data.push(num_bytes);
173        data.append(&mut callback.to_vec()?);
174        data.append(&mut TransactionOptions::to_opt_vec(options)?);
175
176        Ok(Instruction {
177            program_id,
178            accounts,
179            data,
180        })
181    }
182
183    pub fn invoke(
184        &self,
185        program: AccountInfo<'info>,
186        num_bytes: u8,
187        callback: &Callback,
188        options: &Option<TransactionOptions>,
189    ) -> Result<(), solana_program::program_error::ProgramError> {
190        let instruction = self.get_instruction(*program.key, num_bytes, callback, options)?;
191        let account_infos = self.to_account_infos();
192
193        invoke(&instruction, &account_infos[..])
194    }
195
196    pub fn invoke_signed(
197        &self,
198        program: AccountInfo<'info>,
199        num_bytes: u8,
200        callback: &Callback,
201        options: &Option<TransactionOptions>,
202        signer_seeds: &[&[&[u8]]],
203    ) -> Result<(), solana_program::program_error::ProgramError> {
204        let instruction = self.get_instruction(*program.key, num_bytes, callback, options)?;
205        let account_infos = self.to_account_infos();
206
207        invoke_signed(&instruction, &account_infos[..], signer_seeds)
208    }
209
210    fn to_account_infos(&self) -> Vec<AccountInfo<'info>> {
211        vec![
212            self.request.clone(),
213            self.escrow.clone(),
214            self.state.clone(),
215            self.mint.clone(),
216            self.payer.clone(),
217            self.system_program.clone(),
218            self.token_program.clone(),
219            self.associated_token_program.clone(),
220        ]
221    }
222
223    fn to_account_metas(&self) -> Vec<AccountMeta> {
224        vec![
225            AccountMeta::new(*self.request.key, true),
226            AccountMeta::new(*self.escrow.key, false),
227            AccountMeta::new_readonly(*self.state.key, false),
228            AccountMeta::new_readonly(*self.mint.key, false),
229            AccountMeta::new(*self.payer.key, true),
230            AccountMeta::new_readonly(*self.system_program.key, false),
231            AccountMeta::new_readonly(*self.token_program.key, false),
232            AccountMeta::new_readonly(*self.associated_token_program.key, false),
233        ]
234    }
235}
236
237#[derive(Clone, Default, BorshDeserialize, BorshSerialize)]
238pub struct SimpleRandomnessV1Account {
239    pub is_completed: u8,
240    pub num_bytes: u8,
241    pub user: Pubkey,
242    pub escrow: Pubkey,
243    pub request_slot: u64,
244    pub callback: Callback,
245    pub compute_units: u32,
246    pub priority_fee_micro_lamports: u64,
247    pub error_message: String,
248}
249impl SimpleRandomnessV1Account {
250    pub const DISCRIMINATOR: [u8; 8] = [45, 236, 206, 109, 194, 21, 241, 154];
251
252    pub fn discriminator() -> [u8; 8] {
253        Self::DISCRIMINATOR
254    }
255
256    pub fn owner() -> Pubkey {
257        ID
258    }
259
260    pub fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
261        if buf.len() < Self::DISCRIMINATOR.len() {
262            return Err(ProgramError::InvalidAccountData);
263        }
264        let given_disc = &buf[..8];
265        if Self::DISCRIMINATOR != given_disc {
266            return Err(ProgramError::InvalidAccountData);
267        }
268        Self::try_deserialize_unchecked(buf)
269    }
270
271    pub fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError> {
272        let mut data: &[u8] = &buf[8..];
273        Self::deserialize(&mut data).map_err(|_| ProgramError::InvalidAccountData)
274    }
275}
276
277#[derive(Clone, Default, BorshDeserialize, BorshSerialize)]
278pub struct State {
279    pub is_completed: u8,
280    pub num_bytes: u8,
281    pub user: Pubkey,
282    pub escrow: Pubkey,
283    pub request_slot: u64,
284    pub callback: Callback,
285    pub compute_units: u32,
286    pub priority_fee_micro_lamports: u64,
287    pub error_message: String,
288}
289impl State {
290    pub const DISCRIMINATOR: [u8; 8] = [216, 146, 107, 94, 104, 75, 182, 177];
291
292    pub fn discriminator() -> [u8; 8] {
293        Self::DISCRIMINATOR
294    }
295
296    pub fn owner() -> Pubkey {
297        ID
298    }
299
300    pub fn try_deserialize(buf: &mut &[u8]) -> Result<Self, ProgramError> {
301        if buf.len() < Self::DISCRIMINATOR.len() {
302            return Err(ProgramError::InvalidAccountData);
303        }
304        let given_disc = &buf[..8];
305        if Self::DISCRIMINATOR != given_disc {
306            return Err(ProgramError::InvalidAccountData);
307        }
308        Self::try_deserialize_unchecked(buf)
309    }
310
311    pub fn try_deserialize_unchecked(buf: &mut &[u8]) -> Result<Self, ProgramError> {
312        let mut data: &[u8] = &buf[8..];
313        Self::deserialize(&mut data).map_err(|_| ProgramError::InvalidAccountData)
314    }
315}