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, &params))]
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, &params))]
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, &params))]
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, &params))]
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, &params))]
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, &params))]
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, &params))]
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, &params))]
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, &params))]
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, &params))]
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}