Skip to content

Commit c071dfd

Browse files
committed
program: add custom error code for ro writes
1 parent cf11c00 commit c071dfd

File tree

7 files changed

+125
-3
lines changed

7 files changed

+125
-3
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

program/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ test-sbf = []
1919
[dependencies]
2020
bincode = "1.3.3"
2121
bytemuck = "1.14.1"
22+
num-derive = "0.4"
23+
num-traits = "0.2"
2224
serde = { version = "1.0.193", features = ["derive"] }
2325
solana-frozen-abi = { version = "2.0.1", optional = true }
2426
solana-frozen-abi-macro = { version = "2.0.1", optional = true }
2527
solana-program = "2.0.1"
28+
thiserror = "1.0.61"
2629

2730
[dev-dependencies]
2831
mollusk-svm = { version = "0.0.5", features = ["fuzz"] }

program/src/entrypoint.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
//! Program entrypoint
22
33
use {
4-
crate::processor,
5-
solana_program::{account_info::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey},
4+
crate::{error::AddressLookupTableError, processor},
5+
solana_program::{
6+
account_info::AccountInfo, entrypoint::ProgramResult, program_error::PrintProgramError,
7+
pubkey::Pubkey,
8+
},
69
};
710

811
solana_program::entrypoint!(process_instruction);
@@ -11,5 +14,9 @@ fn process_instruction(
1114
accounts: &[AccountInfo],
1215
instruction_data: &[u8],
1316
) -> ProgramResult {
14-
processor::process(program_id, accounts, instruction_data)
17+
if let Err(error) = processor::process(program_id, accounts, instruction_data) {
18+
error.print::<AddressLookupTableError>();
19+
return Err(error);
20+
}
21+
Ok(())
1522
}

program/src/error.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//! Program error types.
2+
3+
use {
4+
num_derive::FromPrimitive,
5+
solana_program::{
6+
decode_error::DecodeError,
7+
msg,
8+
program_error::{PrintProgramError, ProgramError},
9+
},
10+
thiserror::Error,
11+
};
12+
13+
/// Errors that can be returned by the Config program.
14+
#[derive(Error, Clone, Debug, Eq, PartialEq, FromPrimitive)]
15+
pub enum AddressLookupTableError {
16+
/// Instruction modified data of a read-only account.
17+
#[error("Instruction modified data of a read-only account")]
18+
ReadonlyDataModified = 10,
19+
}
20+
21+
impl PrintProgramError for AddressLookupTableError {
22+
fn print<E>(&self) {
23+
msg!(&self.to_string());
24+
}
25+
}
26+
27+
impl From<AddressLookupTableError> for ProgramError {
28+
fn from(e: AddressLookupTableError) -> Self {
29+
ProgramError::Custom(e as u32)
30+
}
31+
}
32+
33+
impl<T> DecodeError<T> for AddressLookupTableError {
34+
fn type_of() -> &'static str {
35+
"AddressLookupTableError"
36+
}
37+
}

program/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#[cfg(all(target_os = "solana", feature = "bpf-entrypoint"))]
55
mod entrypoint;
6+
pub mod error;
67
pub mod instruction;
78
pub mod processor;
89
pub mod state;

program/src/processor.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use {
44
crate::{
55
check_id,
6+
error::AddressLookupTableError,
67
instruction::AddressLookupTableInstruction,
78
state::{
89
AddressLookupTable, ProgramState, LOOKUP_TABLE_MAX_ADDRESSES, LOOKUP_TABLE_META_SIZE,
@@ -342,6 +343,37 @@ fn process_extend_lookup_table(
342343
)
343344
};
344345

346+
// [Core BPF]:
347+
// When a builtin program attempts to write to an executable or read-only
348+
// account, it will be immediately rejected by the `TransactionContext`.
349+
// For more information, see https://github.com/solana-program/config/pull/21.
350+
//
351+
// However, in the case of the Address Lookup Table program's
352+
// `ExtendLookupTable` instruction, since the processor rejects any
353+
// zero-length "new keys" vectors, and will gladly append the same keys
354+
// again to the table, the issue here is slightly different than the linked
355+
// PR.
356+
//
357+
// The builtin version of the Address Lookup Table program will throw
358+
// when it attempts to overwrite the metadata, while the BPF version will
359+
// continue. In the case where an executable or read-only lookup table
360+
// account is provided, and some other requirement below is violated
361+
// (ie. no payer or system program accounts provided, payer is not a
362+
// signer, payer has insufficent balance, etc.), the BPF version will throw
363+
// based on one of those violations, rather than throwing immediately when
364+
// it encounters the executable or read-only lookup table account.
365+
//
366+
// In order to maximize backwards compatibility between the BPF version and
367+
// its original builtin, we add this check from `TransactionContext` to the
368+
// program directly, to throw even when the data being written is the same
369+
// same as what's currently in the account.
370+
//
371+
// Since the account can never be executable and also owned by the ALT
372+
// program, we'll just focus on readonly.
373+
if !lookup_table_info.is_writable {
374+
return Err(AddressLookupTableError::ReadonlyDataModified.into());
375+
}
376+
345377
AddressLookupTable::overwrite_meta_data(
346378
&mut lookup_table_info.try_borrow_mut_data()?[..],
347379
lookup_table_meta,
@@ -419,6 +451,7 @@ fn process_deactivate_lookup_table(program_id: &Pubkey, accounts: &[AccountInfo]
419451
};
420452

421453
let clock = <Clock as Sysvar>::get()?;
454+
422455
lookup_table_meta.deactivation_slot = clock.slot;
423456

424457
AddressLookupTable::overwrite_meta_data(

program/tests/extend_lookup_table_ix.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use {
1010
Mollusk,
1111
},
1212
solana_address_lookup_table_program::{
13+
error::AddressLookupTableError,
1314
instruction::extend_lookup_table,
1415
state::{AddressLookupTable, LookupTableMeta},
1516
},
@@ -355,3 +356,40 @@ fn test_extend_prepaid_lookup_table_without_payer() {
355356
},
356357
);
357358
}
359+
360+
// Backwards compatibility test case.
361+
#[test]
362+
fn test_extend_readonly() {
363+
let mollusk = setup();
364+
365+
let payer = Pubkey::new_unique();
366+
let authority = Pubkey::new_unique();
367+
368+
let initialized_table = new_address_lookup_table(Some(authority), 0);
369+
370+
let lookup_table_address = Pubkey::new_unique();
371+
let lookup_table_account = lookup_table_account(initialized_table);
372+
373+
let new_addresses = vec![Pubkey::new_unique()];
374+
let mut instruction =
375+
extend_lookup_table(lookup_table_address, authority, Some(payer), new_addresses);
376+
377+
// Make the lookup table account read-only.
378+
instruction.accounts[0].is_writable = false;
379+
380+
mollusk.process_and_validate_instruction(
381+
&instruction,
382+
&[
383+
(lookup_table_address, lookup_table_account),
384+
(authority, AccountSharedData::default()),
385+
(
386+
payer,
387+
AccountSharedData::new(100_000_000, 0, &system_program::id()),
388+
),
389+
keyed_account_for_system_program(),
390+
],
391+
&[Check::err(ProgramError::Custom(
392+
AddressLookupTableError::ReadonlyDataModified as u32,
393+
))],
394+
);
395+
}

0 commit comments

Comments
 (0)