orao_solana_vrf_cb/lib.rs
1//! # ORAO VRF Callback
2//!
3//! A crate to interact with the `orao-vrf-cb` smart contract on the Solana network.
4//!
5//! Provides an interface to request verifiable randomness (Ed25519 Signature)
6//! on the Solana network and receive a CPI callback upon fulfill.
7//!
8//! ## Crate features
9//!
10//! * `sdk` (default) — use this feature to build an off-chain client
11//! * `cpi` — use this feature to integrate your program with the oracle
12//!
13//! ```toml
14//! [dependencies.orao-solana-vrf-cb]
15//! version = "..."
16//! default-features = false
17//! features = ["cpi"]
18//! ```
19//!
20//! ## Integration
21//!
22//! The integration process consists of the following steps:
23//!
24//! 1. Write and deploy a client program. It must be able to invoke either [`Request`]
25//! or [`RequestAlt`] instruction via CPI. It might define a callback — in fact any program
26//! instruction could be called.
27//! 2. Register your program as a VRF client by sending the [`Register`] instruction.
28//! 3. Fund a new client created on a previous step (just transfer some SOL to the [`Client`] PDA).
29//! 4. Now you are ready to perform a [`Request`]/[`RequestAlt`] instruction CPI.
30//!
31//! ### Callback functionality
32//!
33//! > ---
34//! >
35//! > #### Side Note
36//! >
37//! > Due to historical reasons callback functionality is split in two parts
38//! > with slightly different capabilities:
39//! >
40//! > 1. [`Callback`] — normal callback
41//! > 2. [`CallbackAlt`] — _ALT_ here stands for [Address Lookup Tables][lookup-tables].
42//! > This type of callback is able to use Solana's feature that allows developers
43//! > to create a collection of related addresses to efficiently load more addresses
44//! > in a single transaction. This is only possible for a _request-level callback_
45//! > (see bellow).
46//! >
47//! > For the same reason there are two kinds of request accounts:
48//! >
49//! > 1. [`RequestAccount`] — request account created by the [`Request`] instruction.
50//! > 2. [`RequestAltAccount`] — request account created by the [`RequestAlt`] instruction.
51//! >
52//! > ---
53//!
54//! #### Account Order Convention
55//!
56//! There is a convention that [`RequestAlt`] instruction follows:
57//!
58//! > ---
59//! >
60//! > **If `n` is the number of Lookup Tables used by a callback then the first `n`
61//! > accounts in the [`Context::remaining_accounts`] list must be the corresponding
62//! > Lookup Table accounts**.
63//! >
64//! > ---
65//!
66//! Namely the instruction accepts the [`RequestAltParams::num_lookup_tables`] parameter
67//! and expects the extended list of accounts (see the "Accounts" section in [`RequestAlt`] docs)
68//!
69//! There is a helper function that simplifies preparing Lookup Table accounts
70//! for the [`RequestAlt`] instruction that is [`parse_lookup_tables`] so you are
71//! encouraged to use it to prepare the CPI call. You also may follow the same
72//! convention if you are using the Anchor Framework:
73//!
74//! ```ignore
75//! let mut cpi_call_remaining_accounts = ctx
76//! .remaining_accounts
77//! .get(..num_lookup_tables as usize)
78//! .expect("call does not follow the convention")
79//! .to_vec();
80//! let lookup_tables = orao_vrf_cb::utils::parse_lookup_tables(&cpi_call_remaining_accounts)?;
81//!
82//! // 1. prepare the callback using `CallbackAlt::compile_accounts`
83//! // and `lookup_tables` obtained above
84//! // 2. extend cpi_call_remaining_accounts with writable accounts if necessary
85//!
86//! let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts)
87//! .with_signer(signers_seeds)
88//! .with_remaining_accounts(cpi_call_remaining_accounts);
89//!
90//! orao_vrf_cb::cpi::request_alt(
91//! cpi_ctx,
92//! RequestAltParams::new(seed)
93//! .with_callback(Some(callback))
94//! .with_num_lookup_tables(num_lookup_tables),
95//! )?;
96//! ```
97//!
98//! #### Callback
99//!
100//! A _callback_ is an instruction invoked via CPI as soon as the randomness request is fulfilled.
101//!
102//! Note that callbacks are optional. You can go without a callback giving `None` during the
103//! client registration. Also note, that there are two levels of callbacks:
104//!
105//! 1. **_Client-level callback_** — defined either upon or after the client registration.
106//! This callback will be used for every [`Request`] (not [`RequestAlt`]) of this client
107//! if not overridden by the _Request-level callback_. Effective client level callback could
108//! be observed in the [`Client::callback`] field of the Client PDA and
109//! could be updated using the [`SetCallback`] instruction.
110//! 2. **_Request-level callback_** — sets a callback for the current request [`Request`]
111//! or [`RequestAlt`] (overrides the _Client-level callback_).
112//!
113//! #### Callback rules
114//!
115//! * callback instruction will be called as an instruction of the registered program
116//! * callback instruction will be called with the following list of accounts:
117//!
118//! 1. The [`Client`] PDA (signer).
119//! 2. The state PDA (writable) (see [`Client::state`]).
120//! 3. The [`NetworkState`] PDA (read-only).
121//! 4. The corresponding [`RequestAccount`] PDA (read-only).
122//! 5. [ ... zero or more _remaining accounts_ (see bellow) ]
123//!
124//! #### Remaining accounts of a callback
125//!
126//! Use the following helpers to add remaining accounts to the callback:
127//!
128//! * **for a normal [`Callback`]** — use [`Callback::with_remaining_account`]
129//! and [`Callback::with_remaining_accounts`] helpers. Or just directly
130//! extend the [`Callback::remaining_accounts`] field.
131//! * **for a [`CallbackAlt`]** — first create a vec of [`RemainingAccount`]s
132//! and then use the [`CallbackAlt::compile_accounts`] helper.
133//!
134//! There exists three kinds of _remaining accounts_:
135//!
136//! 1. **Arbitrary read-only account** — it is possible to give an arbitrary
137//! read-only account to a callback. Use the [`RemainingAccount::readonly`]
138//! constructor to build one.
139//! 2. **Arbitrary writable account** — it is possible to give an arbitrary
140//! writable account as long as it is authorized by the caller - i.e.
141//! if it is given as writable to the corresponding [`Request`]/[`RequestAlt`]
142//! or [`Register`] instruction. Use the [`RemainingAccount::arbitrary_writable`]
143//! constructor.
144//! 3. **Writable PDA** — it is always possible to give a writable account as long
145//! as it is a PDA of the client program - just provide the proper seeds so
146//! that VRF is able to verify the address. Use the [`RemainingAccount::writable`]
147//! constructor.
148//!
149//! #### Callback faults
150//!
151//! If Callback invocation is somehow fails then it is considered as a client misbehavior -
152//! well-written client's callback should never fail.
153//!
154//! There are two kinds of callback faults possible:
155//!
156//! 1. **On-chain fail** — callback failed after the instruction was accepted
157//! by the network. This faulty call is visible on-chain and the logs could
158//! be easily inspected.
159//! 2. **Off-chain fail** — callback failed before the instruction was accepted
160//! by the network. This faulty call is not visible on-chain and appears as
161//! a request that not get fulfilled (in fact it will be fulfilled after the
162//! [`callback_deadline`] reached). We're working on making
163//! it easier to debug such a case.
164//!
165//! In any case the oracle will continue trying to fulfill such a client's request
166//! with increasing interval but eventually will fulfill it without invoking the
167//! Callback at all (see [`NetworkConfiguration::callback_deadline`]).
168//!
169//! ### Clients
170//!
171//! Any program may register any number of clients as long as unique state PDA is used
172//! for every registration. Every registered client maintains a SOL balance used to pay
173//! request fee and rent (note that the rent is reimbursed upon the fulfill).
174//!
175//! It is trivial to fund a client — you should transfer some SOL to its address.
176//! The [`Withdraw`] instruction should be used to withdraw client funds but note
177//! that you couldn't withdraw past the [`Client`] PDA rent.
178//!
179//! The client ownership might be transferred using the [`Transfer`] instruction.
180//!
181//! [lookup-tables]: https://solana.com/ru/developers/guides/advanced/lookup-tables
182//! [`RemainingAccount`]: state::client::RemainingAccount
183//! [`RemainingAccount::readonly`]: state::client::RemainingAccount::readonly
184//! [`RemainingAccount::arbitrary_writable`]: state::client::RemainingAccount::arbitrary_writable
185//! [`RemainingAccount::writable`]: state::client::RemainingAccount::writable
186//! [`Client`]: state::client::Client
187//! [`Client::callback`]: state::client::Client::callback
188//! [`Client::state`]: state::client::Client::state
189//! [`RequestAccount`]: state::request::RequestAccount
190//! [`RequestAltAccount`]: state::request_alt::RequestAltAccount
191//! [`Callback`]: state::client::Callback
192//! [`Callback::remaining_accounts`]: state::client::Callback::remaining_accounts
193//! [`Callback::with_remaining_account`]: state::client::Callback::with_remaining_account
194//! [`Callback::with_remaining_accounts`]: state::client::Callback::with_remaining_accounts
195//! [`CallbackAlt`]: state::client::CallbackAlt
196//! [`CallbackAlt::compile_accounts`]: state::client::CallbackAlt::compile_accounts
197//! [`NetworkState`]: crate::state::network_state::NetworkState
198//! [`NetworkConfiguration::callback_deadline`]: crate::state::network_state::NetworkConfiguration::callback_deadline
199//! [`callback_deadline`]: crate::state::network_state::NetworkConfiguration::callback_deadline
200//! [`parse_lookup_tables`]: crate::utils::parse_lookup_tables
201//! [`Context::remaining_accounts`]: anchor_lang::prelude::Context::remaining_accounts
202#![cfg_attr(docsrs, feature(doc_cfg))]
203
204pub mod constants;
205pub mod error;
206pub mod events;
207pub mod instructions;
208pub mod state;
209pub mod utils;
210
211use anchor_lang::prelude::*;
212
213pub use constants::*;
214pub use instructions::*;
215
216#[cfg_attr(docsrs, doc(cfg(all(feature = "sdk", not(feature = "idl-build")))))]
217pub mod sdk;
218
219declare_id!("VRFCBePmGTpZ234BhbzNNzmyg39Rgdd6VgdfhHwKypU");
220
221/// Helper that checks for Byzantine quorum.
222pub const fn quorum(count: usize, total: usize) -> bool {
223 count >= majority(total)
224}
225
226/// Helper that returns the majority for the given total value.
227pub const fn majority(total: usize) -> usize {
228 total * 2 / 3 + 1
229}
230
231/// Helper that XORes `r` into `l`.
232pub fn xor_array<const N: usize>(l: &mut [u8; N], r: &[u8; N]) {
233 for i in 0..N {
234 l[i] ^= r[i];
235 }
236}
237
238#[program]
239pub mod orao_vrf_cb {
240 use super::*;
241
242 #[access_control(ctx.accounts.validate(&ctx, ¶ms))]
243 pub fn initialize(ctx: Context<Initialize>, params: InitializeParams) -> Result<()> {
244 initialize::initialize_handler(ctx, params)
245 }
246
247 #[access_control(ctx.accounts.validate(&ctx, ¶ms))]
248 pub fn configure(ctx: Context<Configure>, params: ConfigureParams) -> Result<()> {
249 configure::configure_handler(ctx, params)
250 }
251
252 #[access_control(ctx.accounts.validate(&ctx, ¶ms))]
253 pub fn register(ctx: Context<Register>, params: RegisterParams) -> Result<()> {
254 register::register_handler(ctx, params)
255 }
256
257 #[access_control(ctx.accounts.validate(&ctx, ¶ms))]
258 pub fn withdraw(ctx: Context<Withdraw>, params: WithdrawParams) -> Result<()> {
259 withdraw::withdraw_handler(ctx, params)
260 }
261
262 #[access_control(ctx.accounts.validate(&ctx, ¶ms))]
263 pub fn transfer(ctx: Context<Transfer>, params: TransferParams) -> Result<()> {
264 transfer::transfer_handler(ctx, params)
265 }
266
267 #[access_control(ctx.accounts.validate(&ctx, ¶ms))]
268 pub fn set_callback(ctx: Context<SetCallback>, params: SetCallbackParams) -> Result<()> {
269 set_callback::set_callback_handler(ctx, params)
270 }
271
272 #[access_control(ctx.accounts.validate(&ctx, ¶ms))]
273 pub fn request(ctx: Context<Request>, params: RequestParams) -> Result<()> {
274 request::request_handler(ctx, params)
275 }
276
277 #[access_control(ctx.accounts.validate(&ctx, ¶ms))]
278 pub fn request_alt(ctx: Context<RequestAlt>, params: RequestAltParams) -> Result<()> {
279 request_alt::request_alt_handler(ctx, params)
280 }
281
282 #[access_control(ctx.accounts.validate(&ctx, ¶ms))]
283 pub fn fulfill<'a, 'info>(
284 ctx: Context<'a, 'a, 'a, 'info, Fulfill<'info>>,
285 params: FulfillParams,
286 ) -> Result<()> {
287 fulfill::fulfill_handler(ctx, params)
288 }
289
290 #[access_control(ctx.accounts.validate(&ctx, ¶ms))]
291 pub fn fulfill_alt<'a, 'info>(
292 ctx: Context<'a, 'a, 'a, 'info, FulfillAlt<'info>>,
293 params: FulfillAltParams,
294 ) -> Result<()> {
295 fulfill_alt::fulfill_alt_handler(ctx, params)
296 }
297}