gemachain_program/
instruction.rs

1#![allow(clippy::integer_arithmetic)]
2//! Defines a composable Instruction type and a memory-efficient CompiledInstruction.
3
4use crate::sanitize::Sanitize;
5use crate::{pubkey::Pubkey, short_vec};
6use bincode::serialize;
7use borsh::BorshSerialize;
8use serde::Serialize;
9use thiserror::Error;
10
11/// Reasons the runtime might have rejected an instruction.
12///
13/// Instructions errors are included in the bank hashes and therefore are
14/// included as part of the transaction results when determining consensus.
15/// Because of this, members of this enum must not be removed, but new ones can
16/// be added.  Also, it is crucial that meta-information if any that comes along
17/// with an error be consistent across software versions.  For example, it is
18/// dangerous to include error strings from 3rd party crates because they could
19/// change at any time and changes to them are difficult to detect.
20#[derive(
21    Serialize, Deserialize, Debug, Error, PartialEq, Eq, Clone, AbiExample, AbiEnumVisitor,
22)]
23pub enum InstructionError {
24    /// Deprecated! Use CustomError instead!
25    /// The program instruction returned an error
26    #[error("generic instruction error")]
27    GenericError,
28
29    /// The arguments provided to a program were invalid
30    #[error("invalid program argument")]
31    InvalidArgument,
32
33    /// An instruction's data contents were invalid
34    #[error("invalid instruction data")]
35    InvalidInstructionData,
36
37    /// An account's data contents was invalid
38    #[error("invalid account data for instruction")]
39    InvalidAccountData,
40
41    /// An account's data was too small
42    #[error("account data too small for instruction")]
43    AccountDataTooSmall,
44
45    /// An account's balance was too small to complete the instruction
46    #[error("insufficient funds for instruction")]
47    InsufficientFunds,
48
49    /// The account did not have the expected program id
50    #[error("incorrect program id for instruction")]
51    IncorrectProgramId,
52
53    /// A signature was required but not found
54    #[error("missing required signature for instruction")]
55    MissingRequiredSignature,
56
57    /// An initialize instruction was sent to an account that has already been initialized.
58    #[error("instruction requires an uninitialized account")]
59    AccountAlreadyInitialized,
60
61    /// An attempt to operate on an account that hasn't been initialized.
62    #[error("instruction requires an initialized account")]
63    UninitializedAccount,
64
65    /// Program's instruction carat balance does not equal the balance after the instruction
66    #[error("sum of account balances before and after instruction do not match")]
67    UnbalancedInstruction,
68
69    /// Program modified an account's program id
70    #[error("instruction modified the program id of an account")]
71    ModifiedProgramId,
72
73    /// Program spent the carats of an account that doesn't belong to it
74    #[error("instruction spent from the balance of an account it does not own")]
75    ExternalAccountCaratSpend,
76
77    /// Program modified the data of an account that doesn't belong to it
78    #[error("instruction modified data of an account it does not own")]
79    ExternalAccountDataModified,
80
81    /// Read-only account's carats modified
82    #[error("instruction changed the balance of a read-only account")]
83    ReadonlyCaratChange,
84
85    /// Read-only account's data was modified
86    #[error("instruction modified data of a read-only account")]
87    ReadonlyDataModified,
88
89    /// An account was referenced more than once in a single instruction
90    // Deprecated, instructions can now contain duplicate accounts
91    #[error("instruction contains duplicate accounts")]
92    DuplicateAccountIndex,
93
94    /// Executable bit on account changed, but shouldn't have
95    #[error("instruction changed executable bit of an account")]
96    ExecutableModified,
97
98    /// Rent_epoch account changed, but shouldn't have
99    #[error("instruction modified rent epoch of an account")]
100    RentEpochModified,
101
102    /// The instruction expected additional account keys
103    #[error("insufficient account keys for instruction")]
104    NotEnoughAccountKeys,
105
106    /// A non-system program changed the size of the account data
107    #[error("non-system instruction changed account size")]
108    AccountDataSizeChanged,
109
110    /// The instruction expected an executable account
111    #[error("instruction expected an executable account")]
112    AccountNotExecutable,
113
114    /// Failed to borrow a reference to account data, already borrowed
115    #[error("instruction tries to borrow reference for an account which is already borrowed")]
116    AccountBorrowFailed,
117
118    /// Account data has an outstanding reference after a program's execution
119    #[error("instruction left account with an outstanding borrowed reference")]
120    AccountBorrowOutstanding,
121
122    /// The same account was multiply passed to an on-chain program's entrypoint, but the program
123    /// modified them differently.  A program can only modify one instance of the account because
124    /// the runtime cannot determine which changes to pick or how to merge them if both are modified
125    #[error("instruction modifications of multiply-passed account differ")]
126    DuplicateAccountOutOfSync,
127
128    /// Allows on-chain programs to implement program-specific error types and see them returned
129    /// by the Gemachain runtime. A program-specific error may be any type that is represented as
130    /// or serialized to a u32 integer.
131    #[error("custom program error: {0:#x}")]
132    Custom(u32),
133
134    /// The return value from the program was invalid.  Valid errors are either a defined builtin
135    /// error value or a user-defined error in the lower 32 bits.
136    #[error("program returned invalid error code")]
137    InvalidError,
138
139    /// Executable account's data was modified
140    #[error("instruction changed executable accounts data")]
141    ExecutableDataModified,
142
143    /// Executable account's carats modified
144    #[error("instruction changed the balance of a executable account")]
145    ExecutableCaratChange,
146
147    /// Executable accounts must be rent exempt
148    #[error("executable accounts must be rent exempt")]
149    ExecutableAccountNotRentExempt,
150
151    /// Unsupported program id
152    #[error("Unsupported program id")]
153    UnsupportedProgramId,
154
155    /// Cross-program invocation call depth too deep
156    #[error("Cross-program invocation call depth too deep")]
157    CallDepth,
158
159    /// An account required by the instruction is missing
160    #[error("An account required by the instruction is missing")]
161    MissingAccount,
162
163    /// Cross-program invocation reentrancy not allowed for this instruction
164    #[error("Cross-program invocation reentrancy not allowed for this instruction")]
165    ReentrancyNotAllowed,
166
167    /// Length of the seed is too long for address generation
168    #[error("Length of the seed is too long for address generation")]
169    MaxSeedLengthExceeded,
170
171    /// Provided seeds do not result in a valid address
172    #[error("Provided seeds do not result in a valid address")]
173    InvalidSeeds,
174
175    /// Failed to reallocate account data of this length
176    #[error("Failed to reallocate account data")]
177    InvalidRealloc,
178
179    /// Computational budget exceeded
180    #[error("Computational budget exceeded")]
181    ComputationalBudgetExceeded,
182
183    /// Cross-program invocation with unauthorized signer or writable account
184    #[error("Cross-program invocation with unauthorized signer or writable account")]
185    PrivilegeEscalation,
186
187    /// Failed to create program execution environment
188    #[error("Failed to create program execution environment")]
189    ProgramEnvironmentSetupFailure,
190
191    /// Program failed to complete
192    #[error("Program failed to complete")]
193    ProgramFailedToComplete,
194
195    /// Program failed to compile
196    #[error("Program failed to compile")]
197    ProgramFailedToCompile,
198
199    /// Account is immutable
200    #[error("Account is immutable")]
201    Immutable,
202
203    /// Incorrect authority provided
204    #[error("Incorrect authority provided")]
205    IncorrectAuthority,
206
207    /// Failed to serialize or deserialize account data
208    ///
209    /// Warning: This error should never be emitted by the runtime.
210    ///
211    /// This error includes strings from the underlying 3rd party Borsh crate
212    /// which can be dangerous because the error strings could change across
213    /// Borsh versions. Only programs can use this error because they are
214    /// consistent across Gemachain software versions.
215    ///
216    #[error("Failed to serialize or deserialize account data: {0}")]
217    BorshIoError(String),
218
219    /// An account does not have enough carats to be rent-exempt
220    #[error("An account does not have enough carats to be rent-exempt")]
221    AccountNotRentExempt,
222
223    /// Invalid account owner
224    #[error("Invalid account owner")]
225    InvalidAccountOwner,
226
227    /// Program arithmetic overflowed
228    #[error("Program arithmetic overflowed")]
229    ArithmeticOverflow,
230
231    /// Unsupported sysvar
232    #[error("Unsupported sysvar")]
233    UnsupportedSysvar,
234
235    /// Illegal account owner
236    #[error("Provided owner is not allowed")]
237    IllegalOwner,
238    // Note: For any new error added here an equivalent ProgramError and its
239    // conversions must also be added
240}
241
242#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
243pub struct Instruction {
244    /// Pubkey of the instruction processor that executes this instruction
245    pub program_id: Pubkey,
246    /// Metadata for what accounts should be passed to the instruction processor
247    pub accounts: Vec<AccountMeta>,
248    /// Opaque data passed to the instruction processor
249    pub data: Vec<u8>,
250}
251
252impl Instruction {
253    #[deprecated(
254        since = "1.6.0",
255        note = "Please use another Instruction constructor instead, such as `Instruction::new_with_bincode`"
256    )]
257    pub fn new<T: Serialize>(program_id: Pubkey, data: &T, accounts: Vec<AccountMeta>) -> Self {
258        Self::new_with_bincode(program_id, data, accounts)
259    }
260
261    pub fn new_with_bincode<T: Serialize>(
262        program_id: Pubkey,
263        data: &T,
264        accounts: Vec<AccountMeta>,
265    ) -> Self {
266        let data = serialize(data).unwrap();
267        Self {
268            program_id,
269            accounts,
270            data,
271        }
272    }
273
274    pub fn new_with_borsh<T: BorshSerialize>(
275        program_id: Pubkey,
276        data: &T,
277        accounts: Vec<AccountMeta>,
278    ) -> Self {
279        let data = data.try_to_vec().unwrap();
280        Self {
281            program_id,
282            accounts,
283            data,
284        }
285    }
286
287    pub fn new_with_bytes(program_id: Pubkey, data: &[u8], accounts: Vec<AccountMeta>) -> Self {
288        Self {
289            program_id,
290            accounts,
291            data: data.to_vec(),
292        }
293    }
294}
295
296pub fn checked_add(a: u64, b: u64) -> Result<u64, InstructionError> {
297    a.checked_add(b).ok_or(InstructionError::InsufficientFunds)
298}
299
300/// Account metadata used to define Instructions
301#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
302pub struct AccountMeta {
303    /// An account's public key
304    pub pubkey: Pubkey,
305    /// True if an Instruction requires a Transaction signature matching `pubkey`.
306    pub is_signer: bool,
307    /// True if the `pubkey` can be loaded as a read-write account.
308    pub is_writable: bool,
309}
310
311impl AccountMeta {
312    pub fn new(pubkey: Pubkey, is_signer: bool) -> Self {
313        Self {
314            pubkey,
315            is_signer,
316            is_writable: true,
317        }
318    }
319
320    pub fn new_readonly(pubkey: Pubkey, is_signer: bool) -> Self {
321        Self {
322            pubkey,
323            is_signer,
324            is_writable: false,
325        }
326    }
327}
328
329/// An instruction to execute a program
330#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, AbiExample)]
331#[serde(rename_all = "camelCase")]
332pub struct CompiledInstruction {
333    /// Index into the transaction keys array indicating the program account that executes this instruction
334    pub program_id_index: u8,
335    /// Ordered indices into the transaction keys array indicating which accounts to pass to the program
336    #[serde(with = "short_vec")]
337    pub accounts: Vec<u8>,
338    /// The program input data
339    #[serde(with = "short_vec")]
340    pub data: Vec<u8>,
341}
342
343impl Sanitize for CompiledInstruction {}
344
345impl CompiledInstruction {
346    pub fn new<T: Serialize>(program_ids_index: u8, data: &T, accounts: Vec<u8>) -> Self {
347        let data = serialize(data).unwrap();
348        Self {
349            program_id_index: program_ids_index,
350            data,
351            accounts,
352        }
353    }
354
355    pub fn program_id<'a>(&self, program_ids: &'a [Pubkey]) -> &'a Pubkey {
356        &program_ids[self.program_id_index as usize]
357    }
358
359    /// Visit each unique instruction account index once
360    pub fn visit_each_account(
361        &self,
362        work: &mut dyn FnMut(usize, usize) -> Result<(), InstructionError>,
363    ) -> Result<(), InstructionError> {
364        let mut unique_index = 0;
365        'root: for (i, account_index) in self.accounts.iter().enumerate() {
366            // Note: This is an O(n^2) algorithm,
367            // but performed on a very small slice and requires no heap allocations
368            for account_index_before in self.accounts[..i].iter() {
369                if account_index_before == account_index {
370                    continue 'root; // skip dups
371                }
372            }
373            work(unique_index, *account_index as usize)?;
374            unique_index += 1;
375        }
376        Ok(())
377    }
378}
379
380#[cfg(test)]
381mod test {
382    use super::*;
383
384    #[test]
385    fn test_visit_each_account() {
386        let do_work = |accounts: &[u8]| -> (usize, usize) {
387            let mut unique_total = 0;
388            let mut account_total = 0;
389            let mut work = |unique_index: usize, account_index: usize| {
390                unique_total += unique_index;
391                account_total += account_index;
392                Ok(())
393            };
394            let instruction = CompiledInstruction::new(0, &[0], accounts.to_vec());
395            instruction.visit_each_account(&mut work).unwrap();
396
397            (unique_total, account_total)
398        };
399
400        assert_eq!((6, 6), do_work(&[0, 1, 2, 3]));
401        assert_eq!((6, 6), do_work(&[0, 1, 1, 2, 3]));
402        assert_eq!((6, 6), do_work(&[0, 1, 2, 3, 3]));
403        assert_eq!((6, 6), do_work(&[0, 0, 1, 1, 2, 2, 3, 3]));
404        assert_eq!((0, 2), do_work(&[2, 2]));
405    }
406}