Skip to content

Commit f7d6ec1

Browse files
committed
processor: allocate with seed
1 parent ac2a9d3 commit f7d6ec1

File tree

2 files changed

+226
-8
lines changed

2 files changed

+226
-8
lines changed

program/src/processor.rs

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,34 +25,49 @@ macro_rules! accounts {
2525
// Represents an address that may or may not have been generated from a seed.
2626
struct AddressInfo<'a, 'b> {
2727
info: &'a AccountInfo<'b>,
28-
base: Option<&'a AccountInfo<'b>>,
28+
base: Option<(&'a Pubkey, &'a AccountInfo<'b>)>,
2929
}
3030

3131
impl std::fmt::Debug for AddressInfo<'_, '_> {
3232
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3333
f.debug_struct("AddressInfo")
3434
.field("address", &self.info.key)
35-
.field("base", &self.base.map(|info| info.key))
35+
.field("base", &self.base.map(|(key, _)| key))
3636
.finish()
3737
}
3838
}
3939

4040
impl<'a, 'b> AddressInfo<'a, 'b> {
4141
fn is_signer(&self) -> bool {
42-
if let Some(base) = self.base {
43-
base.is_signer
42+
/*
43+
[CORE_BPF]:
44+
This is pretty ugly, but...
45+
46+
For some reason, the program asks for the base address _and_ the
47+
base account, which are supposed to be the same account.
48+
49+
In the original builtin, the key is validated against the
50+
account when this method is called, since it would search the
51+
`signers` hash map for the keyed account matching the provided base
52+
address.
53+
54+
To preserve identical functionality, including the timing at which
55+
errors are thrown, we inject the check here.
56+
*/
57+
if let Some((base_key, base_info)) = self.base {
58+
base_key == base_info.key && base_info.is_signer
4459
} else {
4560
self.info.is_signer
4661
}
4762
}
4863

4964
fn create(
5065
info: &'a AccountInfo<'b>,
51-
with_seed: Option<(&'a AccountInfo<'b>, &str, &Pubkey)>,
66+
with_seed: Option<(&'a Pubkey, &'a AccountInfo<'b>, &str, &Pubkey)>,
5267
) -> Result<Self, ProgramError> {
53-
let base = if let Some((base, seed, owner)) = with_seed {
68+
let base = if let Some((base_key, base_info, seed, owner)) = with_seed {
5469
// Re-derive the address. It must match the supplied address.
55-
let address_with_seed = Pubkey::create_with_seed(base.key, seed, owner)?;
70+
let address_with_seed = Pubkey::create_with_seed(base_key, seed, owner)?;
5671
if *info.key != address_with_seed {
5772
msg!(
5873
"Create: address {} does not match derived address {}",
@@ -61,7 +76,7 @@ impl<'a, 'b> AddressInfo<'a, 'b> {
6176
);
6277
Err(SystemError::AddressWithSeedMismatch)?
6378
}
64-
Some(base)
79+
Some((base_key, base_info))
6580
} else {
6681
None
6782
};
@@ -106,6 +121,22 @@ fn allocate(info: &AccountInfo, address: &AddressInfo, space: u64) -> Result<(),
106121
info.realloc(space as usize, true)
107122
}
108123

124+
fn assign(info: &AccountInfo, address: &AddressInfo, owner: &Pubkey) -> Result<(), ProgramError> {
125+
// No work to do, just return.
126+
if info.owner == owner {
127+
return Ok(());
128+
}
129+
130+
if !address.is_signer() {
131+
msg!("Assign: account {:?} must sign", address);
132+
Err(ProgramError::MissingRequiredSignature)?
133+
}
134+
135+
info.assign(owner);
136+
137+
Ok(())
138+
}
139+
109140
fn process_allocate(accounts: &[AccountInfo], space: u64) -> ProgramResult {
110141
accounts!(
111142
accounts,
@@ -118,6 +149,25 @@ fn process_allocate(accounts: &[AccountInfo], space: u64) -> ProgramResult {
118149
)
119150
}
120151

152+
fn process_allocate_with_seed(
153+
accounts: &[AccountInfo],
154+
base: Pubkey,
155+
seed: String,
156+
space: u64,
157+
owner: Pubkey,
158+
) -> ProgramResult {
159+
accounts!(
160+
accounts,
161+
0 => account_info,
162+
1 => base_info,
163+
);
164+
165+
let address = AddressInfo::create(account_info, Some((&base, base_info, &seed, &owner)))?;
166+
167+
allocate(account_info, &address, space)?;
168+
assign(account_info, &address, &owner)
169+
}
170+
121171
pub fn process(_program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
122172
match solana_bincode::limited_deserialize::<SystemInstruction>(input, MAX_INPUT_LEN)
123173
.map_err(|_| ProgramError::InvalidInstructionData)?
@@ -126,6 +176,15 @@ pub fn process(_program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) ->
126176
msg!("Instruction: Allocate");
127177
process_allocate(accounts, space)
128178
}
179+
SystemInstruction::AllocateWithSeed {
180+
base,
181+
seed,
182+
space,
183+
owner,
184+
} => {
185+
msg!("Instruction: AllocateWithSeed");
186+
process_allocate_with_seed(accounts, base, seed, space, owner)
187+
}
129188
/* TODO: Remaining instruction implementations... */
130189
_ => Err(ProgramError::InvalidInstructionData),
131190
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
mod setup;
2+
3+
use {
4+
mollusk_svm::result::Check,
5+
solana_account::Account,
6+
solana_account_info::MAX_PERMITTED_DATA_INCREASE,
7+
solana_program_error::ProgramError,
8+
solana_pubkey::Pubkey,
9+
solana_system_interface::{
10+
error::SystemError, instruction::allocate_with_seed, MAX_PERMITTED_DATA_LENGTH,
11+
},
12+
};
13+
14+
const BASE: Pubkey = Pubkey::new_from_array([6; 32]);
15+
const OWNER: Pubkey = Pubkey::new_from_array([8; 32]);
16+
const SEED: &str = "seed";
17+
const SPACE: u64 = 10_000;
18+
19+
#[test]
20+
fn fail_address_with_seed_mismatch() {
21+
let mollusk = setup::setup();
22+
23+
let pubkey = Pubkey::new_unique(); // Not derived from base + seed.
24+
25+
mollusk.process_and_validate_instruction(
26+
&allocate_with_seed(&pubkey, &BASE, SEED, SPACE, &OWNER),
27+
&[(pubkey, Account::default()), (BASE, Account::default())],
28+
&[Check::err(ProgramError::Custom(
29+
SystemError::AddressWithSeedMismatch as u32,
30+
))],
31+
);
32+
}
33+
34+
#[test]
35+
fn fail_base_not_signer() {
36+
let mollusk = setup::setup();
37+
38+
let pubkey = Pubkey::create_with_seed(&BASE, &SEED, &OWNER).unwrap();
39+
40+
let mut instruction = allocate_with_seed(&pubkey, &BASE, SEED, SPACE, &OWNER);
41+
instruction.accounts[1].is_signer = false;
42+
43+
mollusk.process_and_validate_instruction(
44+
&instruction,
45+
&[(pubkey, Account::default()), (BASE, Account::default())],
46+
&[Check::err(ProgramError::MissingRequiredSignature)],
47+
);
48+
}
49+
50+
#[test]
51+
fn fail_base_key_mismatch() {
52+
let mollusk = setup::setup();
53+
54+
let pubkey = Pubkey::create_with_seed(&BASE, &SEED, &OWNER).unwrap();
55+
let not_the_base = Pubkey::new_unique();
56+
57+
let mut instruction = allocate_with_seed(&pubkey, &BASE, SEED, SPACE, &OWNER);
58+
instruction.accounts[1].pubkey = not_the_base;
59+
60+
mollusk.process_and_validate_instruction(
61+
&instruction,
62+
&[
63+
(pubkey, Account::default()),
64+
(not_the_base, Account::default()),
65+
],
66+
&[Check::err(ProgramError::MissingRequiredSignature)],
67+
);
68+
}
69+
70+
#[test]
71+
fn fail_account_already_in_use() {
72+
let mollusk = setup::setup();
73+
74+
let pubkey = Pubkey::create_with_seed(&BASE, &SEED, &OWNER).unwrap();
75+
76+
let account = Account {
77+
data: vec![4; 32], // Has data
78+
..Account::default()
79+
};
80+
81+
mollusk.process_and_validate_instruction(
82+
&allocate_with_seed(&pubkey, &BASE, SEED, SPACE, &OWNER),
83+
&[(pubkey, account), (BASE, Account::default())],
84+
&[Check::err(ProgramError::Custom(
85+
SystemError::AccountAlreadyInUse as u32,
86+
))],
87+
);
88+
89+
let account = Account {
90+
owner: Pubkey::new_unique(), // Not System
91+
..Account::default()
92+
};
93+
94+
mollusk.process_and_validate_instruction(
95+
&allocate_with_seed(&pubkey, &BASE, SEED, SPACE, &OWNER),
96+
&[(pubkey, account), (BASE, Account::default())],
97+
&[Check::err(ProgramError::Custom(
98+
SystemError::AccountAlreadyInUse as u32,
99+
))],
100+
);
101+
}
102+
103+
#[test]
104+
fn fail_space_too_large() {
105+
let mollusk = setup::setup();
106+
107+
let pubkey = Pubkey::create_with_seed(&BASE, &SEED, &OWNER).unwrap();
108+
109+
let space_too_large = MAX_PERMITTED_DATA_LENGTH + 1;
110+
111+
mollusk.process_and_validate_instruction(
112+
&allocate_with_seed(&pubkey, &BASE, SEED, space_too_large, &OWNER),
113+
&[(pubkey, Account::default()), (BASE, Account::default())],
114+
&[Check::err(ProgramError::Custom(
115+
SystemError::InvalidAccountDataLength as u32,
116+
))],
117+
);
118+
}
119+
120+
// [TODO: CORE_BPF]: This verifies the concern for the `realloc` issue.
121+
#[test]
122+
fn fail_space_too_large_for_realloc() {
123+
let mollusk = setup::setup();
124+
125+
let pubkey = Pubkey::create_with_seed(&BASE, &SEED, &OWNER).unwrap();
126+
127+
let space_too_large_for_realloc = MAX_PERMITTED_DATA_INCREASE + 1;
128+
129+
mollusk.process_and_validate_instruction(
130+
&allocate_with_seed(
131+
&pubkey,
132+
&BASE,
133+
SEED,
134+
space_too_large_for_realloc as u64,
135+
&OWNER,
136+
),
137+
&[(pubkey, Account::default()), (BASE, Account::default())],
138+
&[Check::err(ProgramError::InvalidRealloc)], // See...?
139+
);
140+
}
141+
142+
#[test]
143+
fn success() {
144+
let mollusk = setup::setup();
145+
146+
let pubkey = Pubkey::create_with_seed(&BASE, &SEED, &OWNER).unwrap();
147+
148+
mollusk.process_and_validate_instruction(
149+
&allocate_with_seed(&pubkey, &BASE, SEED, SPACE, &OWNER),
150+
&[(pubkey, Account::default()), (BASE, Account::default())],
151+
&[
152+
Check::success(),
153+
Check::account(&pubkey)
154+
.owner(&OWNER)
155+
.space(SPACE as usize)
156+
.build(),
157+
],
158+
);
159+
}

0 commit comments

Comments
 (0)