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
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
use {
crate::{
IndexOfAccount, MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION, transaction::TransactionContext,
transaction_accounts::AccountRefMut,
},
solana_account::{ReadableAccount, WritableAccount},
solana_instruction::error::InstructionError,
solana_pubkey::Pubkey,
};
/// Contains account meta data which varies between instruction.
///
/// It also contains indices to other structures for faster lookup.
///
/// This data structure is supposed to be shared with programs in ABIv2, so do not modify it
/// without consulting SIMD-0177.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct InstructionAccount {
/// Points to the account and its key in the `TransactionContext`
pub index_in_transaction: IndexOfAccount,
/// Is this account supposed to sign
is_signer: u8,
/// Is this account allowed to become writable
is_writable: u8,
}
impl InstructionAccount {
pub fn new(
index_in_transaction: IndexOfAccount,
is_signer: bool,
is_writable: bool,
) -> InstructionAccount {
InstructionAccount {
index_in_transaction,
is_signer: is_signer as u8,
is_writable: is_writable as u8,
}
}
pub fn is_signer(&self) -> bool {
self.is_signer != 0
}
pub fn is_writable(&self) -> bool {
self.is_writable != 0
}
pub fn set_is_signer(&mut self, value: bool) {
self.is_signer = value as u8;
}
pub fn set_is_writable(&mut self, value: bool) {
self.is_writable = value as u8;
}
}
/// Shared account borrowed from the TransactionContext and an InstructionContext.
#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
#[derive(Debug)]
pub struct BorrowedInstructionAccount<'a, 'ix_data> {
pub(crate) transaction_context: &'a TransactionContext<'ix_data>,
pub(crate) account: AccountRefMut<'a>,
pub(crate) instruction_account: InstructionAccount,
pub(crate) index_in_transaction_of_instruction_program: IndexOfAccount,
}
#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
impl BorrowedInstructionAccount<'_, '_> {
/// Returns the index of this account (transaction wide)
#[inline]
pub fn get_index_in_transaction(&self) -> IndexOfAccount {
self.instruction_account.index_in_transaction
}
/// Returns the public key of this account (transaction wide)
#[inline]
pub fn get_key(&self) -> &Pubkey {
self.transaction_context
.get_key_of_account_at_index(self.instruction_account.index_in_transaction)
.unwrap()
}
/// Returns the owner of this account (transaction wide)
#[inline]
pub fn get_owner(&self) -> &Pubkey {
self.account.owner()
}
/// Assignes the owner of this account (transaction wide)
pub fn set_owner(&mut self, pubkey: &[u8]) -> Result<(), InstructionError> {
// Only the owner can assign a new owner
if !self.is_owned_by_current_program() {
return Err(InstructionError::ModifiedProgramId);
}
// and only if the account is writable
if !self.is_writable() {
return Err(InstructionError::ModifiedProgramId);
}
// and only if the data is zero-initialized or empty
if !is_zeroed(self.get_data()) {
return Err(InstructionError::ModifiedProgramId);
}
// don't touch the account if the owner does not change
if self.get_owner().to_bytes() == pubkey {
return Ok(());
}
self.touch()?;
self.account.copy_into_owner_from_slice(pubkey);
Ok(())
}
/// Returns the number of lamports of this account (transaction wide)
#[inline]
pub fn get_lamports(&self) -> u64 {
self.account.lamports()
}
/// Overwrites the number of lamports of this account (transaction wide)
pub fn set_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
// An account not owned by the program cannot have its balance decrease
if !self.is_owned_by_current_program() && lamports < self.get_lamports() {
return Err(InstructionError::ExternalAccountLamportSpend);
}
// The balance of read-only may not change
if !self.is_writable() {
return Err(InstructionError::ReadonlyLamportChange);
}
// don't touch the account if the lamports do not change
let old_lamports = self.get_lamports();
if old_lamports == lamports {
return Ok(());
}
let lamports_balance = (lamports as i128).saturating_sub(old_lamports as i128);
self.transaction_context
.accounts
.add_lamports_delta(lamports_balance)?;
self.touch()?;
self.account.set_lamports(lamports);
Ok(())
}
/// Adds lamports to this account (transaction wide)
pub fn checked_add_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
self.set_lamports(
self.get_lamports()
.checked_add(lamports)
.ok_or(InstructionError::ArithmeticOverflow)?,
)
}
/// Subtracts lamports from this account (transaction wide)
pub fn checked_sub_lamports(&mut self, lamports: u64) -> Result<(), InstructionError> {
self.set_lamports(
self.get_lamports()
.checked_sub(lamports)
.ok_or(InstructionError::ArithmeticOverflow)?,
)
}
/// Returns a read-only slice of the account data (transaction wide)
#[inline]
pub fn get_data(&self) -> &[u8] {
self.account.data()
}
/// Returns a writable slice of the account data (transaction wide)
pub fn get_data_mut(&mut self) -> Result<&mut [u8], InstructionError> {
self.can_data_be_changed()?;
self.touch()?;
self.make_data_mut();
Ok(self.account.data_as_mut_slice())
}
/// Overwrites the account data and size (transaction wide).
///
/// Call this when you have a slice of data you do not own and want to
/// replace the account data with it.
pub fn set_data_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
self.can_data_be_resized(data.len())?;
self.touch()?;
self.update_accounts_resize_delta(data.len())?;
// Note that we intentionally don't call self.make_data_mut() here. make_data_mut() will
// allocate + memcpy the current data if self.account is shared. We don't need the memcpy
// here tho because account.set_data_from_slice(data) is going to replace the content
// anyway.
self.account.set_data_from_slice(data);
Ok(())
}
/// Resizes the account data (transaction wide)
///
/// Fills it with zeros at the end if is extended or truncates at the end otherwise.
pub fn set_data_length(&mut self, new_length: usize) -> Result<(), InstructionError> {
self.can_data_be_resized(new_length)?;
// don't touch the account if the length does not change
if self.get_data().len() == new_length {
return Ok(());
}
self.touch()?;
self.update_accounts_resize_delta(new_length)?;
self.account.resize(new_length, 0);
Ok(())
}
/// Appends all elements in a slice to the account
pub fn extend_from_slice(&mut self, data: &[u8]) -> Result<(), InstructionError> {
let new_len = self.get_data().len().saturating_add(data.len());
self.can_data_be_resized(new_len)?;
if data.is_empty() {
return Ok(());
}
self.touch()?;
self.update_accounts_resize_delta(new_len)?;
// Even if extend_from_slice never reduces capacity, still realloc using
// make_data_mut() if necessary so that we grow the account of the full
// max realloc length in one go, avoiding smaller reallocations.
self.make_data_mut();
self.account.extend_from_slice(data);
Ok(())
}
/// Returns whether the underlying AccountSharedData is shared.
///
/// The data is shared if the account has been loaded from the accounts database and has never
/// been written to. Writing to an account unshares it.
///
/// During account serialization, if an account is shared it'll get mapped as CoW, else it'll
/// get mapped directly as writable.
pub fn is_shared(&self) -> bool {
self.account.is_shared()
}
fn make_data_mut(&mut self) {
// if the account is still shared, it means this is the first time we're
// about to write into it. Make the account mutable by copying it in a
// buffer with MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION capacity so that if the
// transaction reallocs, we don't have to copy the whole account data a
// second time to fullfill the realloc.
if self.account.is_shared() {
self.account
.reserve(MAX_ACCOUNT_DATA_GROWTH_PER_INSTRUCTION);
}
}
/// Deserializes the account data into a state
#[cfg(feature = "bincode")]
pub fn get_state<T: serde::de::DeserializeOwned>(&self) -> Result<T, InstructionError> {
bincode::deserialize(self.account.data()).map_err(|_| InstructionError::InvalidAccountData)
}
/// Serializes a state into the account data
#[cfg(feature = "bincode")]
pub fn set_state<T: serde::Serialize>(&mut self, state: &T) -> Result<(), InstructionError> {
let data = self.get_data_mut()?;
let serialized_size =
bincode::serialized_size(state).map_err(|_| InstructionError::GenericError)?;
if serialized_size > data.len() as u64 {
return Err(InstructionError::AccountDataTooSmall);
}
bincode::serialize_into(&mut *data, state).map_err(|_| InstructionError::GenericError)?;
Ok(())
}
// Returns whether or the lamports currently in the account is sufficient for rent exemption should the
// data be resized to the given size
pub fn is_rent_exempt_at_data_length(&self, data_length: usize) -> bool {
self.transaction_context
.rent
.is_exempt(self.get_lamports(), data_length)
}
/// Returns whether this account is executable (transaction wide)
#[inline]
#[deprecated(since = "2.1.0", note = "Use `get_owner` instead")]
pub fn is_executable(&self) -> bool {
#[allow(deprecated)]
self.account.executable()
}
/// Configures whether this account is executable (transaction wide)
pub fn set_executable(&mut self, is_executable: bool) -> Result<(), InstructionError> {
// To become executable an account must be rent exempt
if !self
.transaction_context
.rent
.is_exempt(self.get_lamports(), self.get_data().len())
{
return Err(InstructionError::ExecutableAccountNotRentExempt);
}
// Only the owner can set the executable flag
if !self.is_owned_by_current_program() {
return Err(InstructionError::ExecutableModified);
}
// and only if the account is writable
if !self.is_writable() {
return Err(InstructionError::ExecutableModified);
}
// don't touch the account if the executable flag does not change
#[allow(deprecated)]
if self.is_executable() == is_executable {
return Ok(());
}
self.touch()?;
self.account.set_executable(is_executable);
Ok(())
}
/// Returns the rent epoch of this account (transaction wide)
#[inline]
pub fn get_rent_epoch(&self) -> u64 {
self.account.rent_epoch()
}
/// Returns whether this account is a signer (instruction wide)
pub fn is_signer(&self) -> bool {
self.instruction_account.is_signer()
}
/// Returns whether this account is writable (instruction wide)
pub fn is_writable(&self) -> bool {
self.instruction_account.is_writable()
}
/// Returns true if the owner of this account is the current `InstructionContext`s last program (instruction wide)
pub fn is_owned_by_current_program(&self) -> bool {
self.transaction_context
.get_key_of_account_at_index(self.index_in_transaction_of_instruction_program)
.map(|program_key| program_key == self.get_owner())
.unwrap_or_default()
}
/// Returns an error if the account data can not be mutated by the current program
pub fn can_data_be_changed(&self) -> Result<(), InstructionError> {
// and only if the account is writable
if !self.is_writable() {
return Err(InstructionError::ReadonlyDataModified);
}
// and only if we are the owner
if !self.is_owned_by_current_program() {
return Err(InstructionError::ExternalAccountDataModified);
}
Ok(())
}
/// Returns an error if the account data can not be resized to the given length
pub fn can_data_be_resized(&self, new_len: usize) -> Result<(), InstructionError> {
let old_len = self.get_data().len();
// Only the owner can change the length of the data
if new_len != old_len && !self.is_owned_by_current_program() {
return Err(InstructionError::AccountDataSizeChanged);
}
self.transaction_context
.accounts
.can_data_be_resized(old_len, new_len)?;
self.can_data_be_changed()
}
fn touch(&self) -> Result<(), InstructionError> {
self.transaction_context
.accounts
.touch(self.instruction_account.index_in_transaction)
}
fn update_accounts_resize_delta(&mut self, new_len: usize) -> Result<(), InstructionError> {
self.transaction_context
.accounts
.update_accounts_resize_delta(self.get_data().len(), new_len)
}
}
#[cfg(not(any(target_arch = "bpf", target_arch = "sbf")))]
fn is_zeroed(buf: &[u8]) -> bool {
const ZEROS_LEN: usize = 1024;
const ZEROS: [u8; ZEROS_LEN] = [0; ZEROS_LEN];
let mut chunks = buf.chunks_exact(ZEROS_LEN);
#[allow(clippy::indexing_slicing)]
{
chunks.all(|chunk| chunk == &ZEROS[..])
&& chunks.remainder() == &ZEROS[..chunks.remainder().len()]
}
}