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}