Skip to content

Commit 71de5e1

Browse files
authored
fix: deterministic account compilation with bounds checking (#180)
Signed-off-by: AvhiMaz <avhimazumder5@outlook.com>
1 parent 50d2a3d commit 71de5e1

File tree

3 files changed

+104
-10
lines changed

3 files changed

+104
-10
lines changed

error/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ pub enum MolluskError<'a> {
2424
/// Program targeted by the instruction is missing from the cache.
2525
#[error(" [MOLLUSK]: Program targeted by the instruction is missing from the cache: {0}")]
2626
ProgramNotCached(&'a Pubkey),
27+
/// Program ID required by the instruction is not mapped in the key map.
28+
#[error(" [MOLLUSK]: Program ID required by the instruction is not mapped: {0}")]
29+
ProgramIdNotMapped(&'a Pubkey),
30+
/// Account index exceeds maximum (255).
31+
#[error(" [MOLLUSK]: Account index exceeds maximum of 255: {0}")]
32+
AccountIndexOverflow(usize),
2733
}
2834

2935
pub trait MolluskPanic<T> {

keys/src/accounts.rs

Lines changed: 93 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,28 @@ pub fn compile_instruction_without_data(
1919
key_map: &KeyMap,
2020
instruction: &Instruction,
2121
) -> CompiledInstructionWithoutData {
22+
let program_id_index = key_map
23+
.position(&instruction.program_id)
24+
.or_panic_with(MolluskError::ProgramIdNotMapped(&instruction.program_id));
25+
26+
let program_id_index = u8::try_from(program_id_index)
27+
.or_panic_with(MolluskError::AccountIndexOverflow(program_id_index));
28+
29+
let accounts: Vec<u8> = instruction
30+
.accounts
31+
.iter()
32+
.map(|account_meta| {
33+
let index = key_map
34+
.position(&account_meta.pubkey)
35+
.or_panic_with(MolluskError::AccountMissing(&account_meta.pubkey));
36+
37+
u8::try_from(index).or_panic_with(MolluskError::AccountIndexOverflow(index))
38+
})
39+
.collect();
40+
2241
CompiledInstructionWithoutData {
23-
program_id_index: key_map.position(&instruction.program_id).unwrap() as u8,
24-
accounts: instruction
25-
.accounts
26-
.iter()
27-
.map(|account_meta| key_map.position(&account_meta.pubkey).unwrap() as u8)
28-
.collect(),
42+
program_id_index,
43+
accounts,
2944
}
3045
}
3146

@@ -381,4 +396,76 @@ mod tests {
381396
let acc = result.iter().find(|(pk, _)| pk == &account1).unwrap();
382397
assert_eq!(acc.1.lamports(), 100);
383398
}
399+
400+
#[test]
401+
fn test_compile_instruction_without_data_deterministic() {
402+
let program_id = Pubkey::new_unique();
403+
let account1 = Pubkey::new_unique();
404+
let account2 = Pubkey::new_unique();
405+
let account3 = Pubkey::new_unique();
406+
407+
let instruction = test_instruction(program_id, &[account1, account2, account3]);
408+
409+
let key_map1 = KeyMap::compile_from_instruction(&instruction);
410+
let compiled1 = compile_instruction_without_data(&key_map1, &instruction);
411+
412+
let key_map2 = KeyMap::compile_from_instruction(&instruction);
413+
let compiled2 = compile_instruction_without_data(&key_map2, &instruction);
414+
415+
assert_eq!(compiled1.program_id_index, compiled2.program_id_index);
416+
assert_eq!(compiled1.accounts, compiled2.accounts);
417+
}
418+
419+
#[test]
420+
#[should_panic(expected = "Account index exceeds maximum of 255")]
421+
fn test_compile_instruction_without_data_account_index_overflow() {
422+
let mut key_map = KeyMap::default();
423+
424+
for _ in 0..256 {
425+
let pubkey = Pubkey::new_unique();
426+
key_map.add_program(pubkey);
427+
}
428+
429+
let program_id = Pubkey::new_unique();
430+
key_map.add_program(program_id);
431+
432+
let instruction = Instruction::new_with_bytes(program_id, &[], vec![]);
433+
434+
let _ = compile_instruction_without_data(&key_map, &instruction);
435+
}
436+
437+
#[test]
438+
#[should_panic(expected = "Program ID required by the instruction is not mapped")]
439+
fn test_compile_instruction_without_data_missing_program_id() {
440+
let program_id = Pubkey::new_unique();
441+
let account1 = Pubkey::new_unique();
442+
let instruction = test_instruction(program_id, &[account1]);
443+
444+
let mut key_map = KeyMap::default();
445+
key_map.add_account(&AccountMeta::new(account1, false));
446+
447+
let _ = compile_instruction_without_data(&key_map, &instruction);
448+
}
449+
450+
#[test]
451+
#[should_panic(expected = "An account required by the instruction was not provided")]
452+
fn test_compile_instruction_without_data_missing_account() {
453+
let program_id = Pubkey::new_unique();
454+
let account1 = Pubkey::new_unique();
455+
let account_missing = Pubkey::new_unique();
456+
let instruction = Instruction::new_with_bytes(
457+
program_id,
458+
&[],
459+
vec![
460+
AccountMeta::new(account1, false),
461+
AccountMeta::new(account_missing, false),
462+
],
463+
);
464+
465+
let mut key_map = KeyMap::default();
466+
key_map.add_program(program_id);
467+
key_map.add_account(&AccountMeta::new(account1, false));
468+
469+
let _ = compile_instruction_without_data(&key_map, &instruction);
470+
}
384471
}

keys/src/keys.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,20 @@
2323
use {
2424
solana_instruction::{AccountMeta, Instruction},
2525
solana_pubkey::Pubkey,
26-
std::collections::{HashMap, HashSet},
26+
std::collections::{BTreeMap, HashSet},
2727
};
2828

29-
/// Wrapper around a hashmap of account keys and their corresponding roles
29+
/// Wrapper around a btree map of account keys and their corresponding roles
3030
/// (`is_signer`, `is_writable`).
3131
///
3232
/// On compilation, keys are awarded the highest role they are assigned in the
33-
/// transaction, and the hash map provides deduplication.
33+
/// transaction, and the btree map provides deduplication and deterministic
34+
/// ordering.
3435
///
3536
/// The map can be queried by key for `is_signer` and `is_writable` roles.
3637
#[derive(Debug, Default)]
3738
pub struct KeyMap {
38-
map: HashMap<Pubkey, (bool, bool)>,
39+
map: BTreeMap<Pubkey, (bool, bool)>,
3940
program_ids: HashSet<Pubkey>,
4041
}
4142

0 commit comments

Comments
 (0)