solders_instruction/
lib.rs

1use std::hash::Hasher;
2
3use derive_more::{From, Into};
4use pyo3::{prelude::*, types::PyBytes};
5use serde::{Deserialize, Serialize};
6use solana_program::{
7    instruction::{
8        AccountMeta as AccountMetaOriginal, CompiledInstruction as CompiledInstructionOriginal,
9        Instruction as InstructionOriginal,
10    },
11    pubkey::Pubkey as PubkeyOriginal,
12};
13use solders_macros::{common_methods, pyhash, richcmp_eq_only};
14use solders_pubkey::Pubkey;
15
16use solders_traits_core::{
17    impl_display, py_from_bytes_general_via_bincode, pybytes_general_via_bincode,
18    CommonMethodsCore, PyHash, RichcmpEqualityOnly,
19};
20
21/// Describes a single account read or written by a program during instruction
22/// execution.
23///
24/// When constructing an :class:`Instruction`, a list of all accounts that may be
25/// read or written during the execution of that instruction must be supplied.
26/// Any account that may be mutated by the program during execution, either its
27/// data or metadata such as held lamports, must be writable.
28///
29/// Note that because the Solana runtime schedules parallel transaction
30/// execution around which accounts are writable, care should be taken that only
31/// accounts which actually may be mutated are specified as writable.
32///
33/// Args:
34///     pubkey (Pubkey): An account's public key.
35///     is_signer (bool): True if an :class:`Instruction` requires a :class:`~solders.transaction.Transaction`
36///         signature matching ``pubkey``.
37///     is_writable (bool): True if the account data or metadata may be mutated during program execution.
38///
39/// Example:
40///     >>> from solders.pubkey import Pubkey
41///     >>> from solders.instruction import AccountMeta, Instruction
42///     >>> from_pubkey = Pubkey.new_unique()
43///     >>> to_pubkey = Pubkey.new_unique()
44///     >>> program_id = Pubkey.new_unique()
45///     >>> instruction_data = bytes([1])
46///     >>> accs = [AccountMeta(from_pubkey, is_signer=True, is_writable=True), AccountMeta(to_pubkey, is_signer=True, is_writable=True)]
47///     >>> instruction = Instruction(program_id, instruction_data, accs)
48///
49#[pyclass(module = "solders.instruction", subclass)]
50#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, From, Into)]
51pub struct AccountMeta(AccountMetaOriginal);
52#[pyhash]
53#[richcmp_eq_only]
54#[common_methods]
55#[pymethods]
56impl AccountMeta {
57    #[new]
58    pub fn new(pubkey: &Pubkey, is_signer: bool, is_writable: bool) -> Self {
59        let underlying_pubkey = pubkey.into();
60        let underlying = if is_writable {
61            AccountMetaOriginal::new(underlying_pubkey, is_signer)
62        } else {
63            AccountMetaOriginal::new_readonly(underlying_pubkey, is_signer)
64        };
65        underlying.into()
66    }
67
68    #[getter]
69    pub fn pubkey(&self) -> Pubkey {
70        self.0.pubkey.into()
71    }
72
73    #[getter]
74    pub fn is_signer(&self) -> bool {
75        self.0.is_signer
76    }
77
78    #[getter]
79    pub fn is_writable(&self) -> bool {
80        self.0.is_writable
81    }
82
83    #[staticmethod]
84    /// Deserialize a serialized ``AccountMeta`` object.
85    ///
86    /// Args:
87    ///     data (bytes): the serialized ``AccountMeta``.
88    ///
89    /// Returns:
90    ///     AccountMeta: the deserialized ``AccountMeta``.
91    ///
92    pub fn from_bytes(data: &[u8]) -> PyResult<Self> {
93        Self::py_from_bytes(data)
94    }
95}
96pybytes_general_via_bincode!(AccountMeta);
97impl RichcmpEqualityOnly for AccountMeta {}
98py_from_bytes_general_via_bincode!(AccountMeta);
99
100solders_traits_core::common_methods_default!(AccountMeta);
101
102impl PyHash for AccountMeta {}
103impl_display!(AccountMeta);
104
105#[allow(clippy::derived_hash_with_manual_eq)]
106impl std::hash::Hash for AccountMeta {
107    fn hash<H: Hasher>(&self, state: &mut H) {
108        self.0.pubkey.hash(state);
109        self.0.is_signer.hash(state);
110        self.0.is_writable.hash(state);
111    }
112}
113
114#[pyclass(module = "solders.instruction", subclass)]
115/// A directive for a single invocation of a Solana program.
116///
117/// An instruction specifies which program it is calling, which accounts it may
118/// read or modify, and additional data that serves as input to the program. One
119/// or more instructions are included in transactions submitted by Solana
120/// clients. Instructions are also used to describe `cross-program
121/// invocations <https://docs.solana.com/developing/programming-model/calling-between-programs/>`_.
122///
123/// During execution, a program will receive a list of account data as one of
124/// its arguments, in the same order as specified during ``Instruction``
125/// construction.
126///
127/// While Solana is agnostic to the format of the instruction data, it has
128/// built-in support for serialization via
129/// `borsh <https://docs.rs/borsh/latest/borsh/>`_
130/// and `bincode <https://docs.rs/bincode/latest/bincode/>`_.
131///
132/// When constructing an ``Instruction``, a list of all accounts that may be
133/// read or written during the execution of that instruction must be supplied as
134/// :class:`AccountMeta` values.
135///
136/// **Specifying Account Metadata**
137///
138/// Any account whose data may be mutated by the program during execution must
139/// be specified as writable. During execution, writing to an account that was
140/// not specified as writable will cause the transaction to fail. Writing to an
141/// account that is not owned by the program will cause the transaction to fail.
142///
143/// Any account whose lamport balance may be mutated by the program during
144/// execution must be specified as writable. During execution, mutating the
145/// lamports of an account that was not specified as writable will cause the
146/// transaction to fail. While *subtracting* lamports from an account not owned
147/// by the program will cause the transaction to fail, *adding* lamports to any
148/// account is allowed, as long is it is mutable.
149///
150/// Accounts that are not read or written by the program may still be specified
151/// in an ``Instruction``'s account list. These will affect scheduling of program
152/// execution by the runtime, but will otherwise be ignored.
153///
154/// When building a transaction, the Solana runtime coalesces all accounts used
155/// by all instructions in that transaction, along with accounts and permissions
156/// required by the runtime, into a single account list. Some accounts and
157/// account permissions required by the runtime to process a transaction are
158/// *not* required to be included in an ``Instruction``'s account list. These
159/// include:
160///
161/// * The program ID: it is a separate field of ``Instruction``
162/// * The transaction's fee-paying account: it is added during :class:`~solders.message.Message`
163///   construction. A program may still require the fee payer as part of the
164///   account list if it directly references it.
165///
166///
167/// Programs may require signatures from some accounts, in which case they
168/// should be specified as signers during ``Instruction`` construction. The
169/// program must still validate during execution that the account is a signer.
170///
171/// Args:
172///     program_id (Pubkey): Pubkey of the program that executes this instruction.
173///     data (bytes): Opaque data passed to the program for its own interpretation.
174///     accounts (list[AccountMeta]): Metadata describing accounts that should be passed to the program.
175///
176#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, From, Into)]
177pub struct Instruction(pub InstructionOriginal);
178
179#[richcmp_eq_only]
180#[common_methods]
181#[pymethods]
182impl Instruction {
183    #[new]
184    pub fn new(program_id: &Pubkey, data: &[u8], accounts: Vec<AccountMeta>) -> Self {
185        let underlying_accounts: Vec<AccountMetaOriginal> =
186            accounts.into_iter().map(|x| x.0).collect();
187        let underlying =
188            InstructionOriginal::new_with_bytes(program_id.into(), data, underlying_accounts);
189        underlying.into()
190    }
191
192    #[getter]
193    pub fn program_id(&self) -> Pubkey {
194        self.0.program_id.into()
195    }
196
197    #[getter]
198    pub fn data<'a>(&self, py: Python<'a>) -> &'a PyBytes {
199        PyBytes::new(py, &self.0.data)
200    }
201
202    #[getter]
203    pub fn accounts(&self) -> Vec<AccountMeta> {
204        self.0
205            .accounts
206            .clone()
207            .into_iter()
208            .map(AccountMeta)
209            .collect()
210    }
211
212    #[setter]
213    pub fn set_accounts(&mut self, accounts: Vec<AccountMeta>) {
214        self.0.accounts = accounts
215            .into_iter()
216            .map(AccountMetaOriginal::from)
217            .collect();
218    }
219
220    #[staticmethod]
221    /// Deserialize a serialized ``Instruction`` object.
222    ///
223    /// Args:
224    ///     data (bytes): the serialized ``Instruction``.
225    ///
226    /// Returns:
227    ///     Instruction: the deserialized ``Instruction``.
228    ///
229    /// Example:
230    ///     >>> from solders.pubkey import Pubkey
231    ///     >>> from solders.instruction import AccountMeta, Instruction
232    ///     >>> from_pubkey = Pubkey.new_unique()
233    ///     >>> to_pubkey = Pubkey.new_unique()
234    ///     >>> program_id = Pubkey.new_unique()
235    ///     >>> instruction_data = bytes([1])
236    ///     >>> accounts = [AccountMeta(from_pubkey, is_signer=True, is_writable=True), AccountMeta(to_pubkey, is_signer=True, is_writable=True),]
237    ///     >>> instruction = Instruction(program_id, instruction_data, accounts)
238    ///     >>> serialized = bytes(instruction)
239    ///     >>> assert Instruction.from_bytes(serialized) == instruction
240    ///
241    pub fn from_bytes(data: &[u8]) -> PyResult<Self> {
242        Self::py_from_bytes(data)
243    }
244}
245pybytes_general_via_bincode!(Instruction);
246impl RichcmpEqualityOnly for Instruction {}
247py_from_bytes_general_via_bincode!(Instruction);
248
249solders_traits_core::common_methods_default!(Instruction);
250
251impl_display!(Instruction);
252
253impl AsRef<InstructionOriginal> for Instruction {
254    fn as_ref(&self) -> &InstructionOriginal {
255        &self.0
256    }
257}
258
259/// A compact encoding of an instruction.
260///
261/// A ``CompiledInstruction`` is a component of a multi-instruction :class:`~solders.message.Message`,
262/// which is the core of a Solana transaction. It is created during the
263/// construction of ``Message``. Most users will not interact with it directly.
264///
265/// Args:
266///     program_id_index (int): Index into the transaction keys array indicating the
267///         program account that executes this instruction.
268///     data (bytes): The program input data.
269///     accounts (bytes): Ordered indices into the transaction keys array indicating
270///         which accounts to pass to the program.
271///
272#[pyclass(module = "solders.instruction", subclass)]
273#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, From, Into)]
274pub struct CompiledInstruction(CompiledInstructionOriginal);
275
276#[richcmp_eq_only]
277#[common_methods]
278#[pymethods]
279impl CompiledInstruction {
280    #[new]
281    pub fn new(program_id_index: u8, data: &[u8], accounts: &[u8]) -> Self {
282        CompiledInstructionOriginal::new_from_raw_parts(
283            program_id_index,
284            data.to_vec(),
285            accounts.to_vec(),
286        )
287        .into()
288    }
289
290    /// Return the pubkey of the program that executes this instruction.
291    ///
292    /// Returns:
293    ///     Pubkey: The program ID.
294    ///
295    pub fn program_id(&self, program_ids: Vec<Pubkey>) -> Pubkey {
296        let underlying_pubkeys: Vec<PubkeyOriginal> =
297            program_ids.iter().map(PubkeyOriginal::from).collect();
298        let underlying = *self.0.program_id(&underlying_pubkeys);
299        underlying.into()
300    }
301
302    #[getter]
303    pub fn program_id_index(&self) -> u8 {
304        self.0.program_id_index
305    }
306
307    #[getter]
308    pub fn accounts<'a>(&self, py: Python<'a>) -> &'a PyBytes {
309        PyBytes::new(py, &self.0.accounts)
310    }
311
312    #[setter]
313    pub fn set_accounts(&mut self, accounts: Vec<u8>) {
314        self.0.accounts = accounts
315    }
316
317    #[getter]
318    pub fn data<'a>(&self, py: Python<'a>) -> &'a PyBytes {
319        PyBytes::new(py, &self.0.data)
320    }
321
322    #[staticmethod]
323    /// Deserialize a serialized ``CompiledInstruction`` object.
324    ///
325    /// Args:
326    ///     data (bytes): the serialized ``CompiledInstruction``.
327    ///
328    /// Returns:
329    ///     CompiledInstruction: The deserialized ``CompiledInstruction``.
330    ///
331    pub fn from_bytes(data: &[u8]) -> PyResult<Self> {
332        Self::py_from_bytes(data)
333    }
334}
335pybytes_general_via_bincode!(CompiledInstruction);
336impl RichcmpEqualityOnly for CompiledInstruction {}
337py_from_bytes_general_via_bincode!(CompiledInstruction);
338
339solders_traits_core::common_methods_default!(CompiledInstruction);
340
341impl_display!(CompiledInstruction);
342
343impl AsRef<CompiledInstructionOriginal> for CompiledInstruction {
344    fn as_ref(&self) -> &CompiledInstructionOriginal {
345        &self.0
346    }
347}
348
349pub fn convert_instructions(instructions: Vec<Instruction>) -> Vec<InstructionOriginal> {
350    instructions
351        .into_iter()
352        .map(InstructionOriginal::from)
353        .collect()
354}