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