Skip to content

Commit 5a90ad9

Browse files
authored
accumulator-updater 8/x] - whitelist verification pda auth (#775)
* feat(accumulator-updater): change whitelist verification to use a signed PDA * feat(accumulator-updater): address PR feedback remove "auth" as seed
1 parent 7b0851f commit 5a90ad9

File tree

7 files changed

+105
-62
lines changed

7 files changed

+105
-62
lines changed

accumulator_updater/programs/accumulator_updater/src/instructions/put_all.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub fn put_all<'info>(
2222
base_account_key: Pubkey,
2323
messages: Vec<Vec<u8>>,
2424
) -> Result<()> {
25-
let cpi_caller = ctx.accounts.whitelist_verifier.is_allowed()?;
25+
let cpi_caller_auth = ctx.accounts.whitelist_verifier.is_allowed()?;
2626
let accumulator_input_ai = ctx
2727
.remaining_accounts
2828
.first()
@@ -34,15 +34,15 @@ pub fn put_all<'info>(
3434
let accumulator_input = &mut (if is_uninitialized_account(accumulator_input_ai) {
3535
let (pda, bump) = Pubkey::find_program_address(
3636
&[
37-
cpi_caller.as_ref(),
37+
cpi_caller_auth.as_ref(),
3838
ACCUMULATOR.as_bytes(),
3939
base_account_key.as_ref(),
4040
],
4141
&crate::ID,
4242
);
4343
require_keys_eq!(accumulator_input_ai.key(), pda);
4444
let signer_seeds = [
45-
cpi_caller.as_ref(),
45+
cpi_caller_auth.as_ref(),
4646
ACCUMULATOR.as_bytes(),
4747
base_account_key.as_ref(),
4848
&[bump],
@@ -56,7 +56,6 @@ pub fn put_all<'info>(
5656
accumulator_input_ai,
5757
8 + AccumulatorInput::INIT_SPACE,
5858
&ctx.accounts.fund,
59-
// seeds,
6059
&[signer_seeds.as_slice(), fund_signer_seeds.as_slice()],
6160
&ctx.accounts.system_program,
6261
)?;
@@ -75,7 +74,11 @@ pub fn put_all<'info>(
7574
});
7675
// note: redundant for uninitialized code path but safer to check here.
7776
// compute budget cost should be minimal
78-
accumulator_input.validate(accumulator_input_ai.key(), cpi_caller, base_account_key)?;
77+
accumulator_input.validate(
78+
accumulator_input_ai.key(),
79+
cpi_caller_auth,
80+
base_account_key,
81+
)?;
7982

8083

8184
let (num_msgs, num_bytes) = accumulator_input.put_all(&messages);

accumulator_updater/programs/accumulator_updater/src/state/accumulator_input.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,6 @@ impl AccumulatorHeader {
6666
}
6767
}
6868
impl AccumulatorInput {
69-
pub fn size(&self) -> usize {
70-
AccumulatorHeader::INIT_SPACE + 4 + self.messages.len()
71-
}
72-
7369
pub fn new(bump: u8) -> Self {
7470
let header = AccumulatorHeader::new(bump);
7571
Self {

accumulator_updater/programs/accumulator_updater/src/state/whitelist.rs

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
use {
22
crate::AccumulatorUpdaterError,
3-
anchor_lang::{
4-
prelude::*,
5-
solana_program::sysvar::{
6-
self,
7-
instructions::get_instruction_relative,
8-
},
9-
},
3+
anchor_lang::prelude::*,
104
};
115

126
// Note: purposely not making this zero_copy
@@ -49,23 +43,18 @@ pub struct WhitelistVerifier<'info> {
4943
)]
5044
// Using a Box to move account from stack to heap
5145
pub whitelist: Box<Account<'info, Whitelist>>,
52-
/// CHECK: Instruction introspection sysvar
53-
#[account(address = sysvar::instructions::ID)]
54-
pub ixs_sysvar: UncheckedAccount<'info>,
46+
/// PDA representing authorized cpi caller
47+
pub cpi_caller_auth: Signer<'info>,
5548
}
5649

5750
impl<'info> WhitelistVerifier<'info> {
58-
pub fn get_cpi_caller(&self) -> Result<Pubkey> {
59-
let instruction = get_instruction_relative(0, &self.ixs_sysvar.to_account_info())?;
60-
Ok(instruction.program_id)
61-
}
6251
pub fn is_allowed(&self) -> Result<Pubkey> {
63-
let cpi_caller = self.get_cpi_caller()?;
52+
let auth = self.cpi_caller_auth.key();
6453
let whitelist = &self.whitelist;
6554
require!(
66-
whitelist.allowed_programs.contains(&cpi_caller),
55+
whitelist.allowed_programs.contains(&auth),
6756
AccumulatorUpdaterError::CallerNotAllowed
6857
);
69-
Ok(cpi_caller)
58+
Ok(auth)
7059
}
7160
}

accumulator_updater/programs/mock-cpi-caller/src/instructions/add_price.rs

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use {
33
instructions::{
44
sighash,
55
ACCUMULATOR_UPDATER_IX_NAME,
6+
CPI,
67
},
78
message::{
89
get_schemas,
@@ -20,7 +21,7 @@ use {
2021
accumulator_updater::program::AccumulatorUpdater as AccumulatorUpdaterProgram,
2122
anchor_lang::{
2223
prelude::*,
23-
solana_program::sysvar,
24+
system_program,
2425
},
2526
};
2627

@@ -63,7 +64,7 @@ impl<'info> AddPrice<'info> {
6364
let mut accounts = vec![
6465
AccountMeta::new(ctx.accounts.fund.key(), false),
6566
AccountMeta::new_readonly(ctx.accounts.accumulator_whitelist.key(), false),
66-
AccountMeta::new_readonly(ctx.accounts.ixs_sysvar.key(), false),
67+
AccountMeta::new_readonly(ctx.accounts.auth.key(), true),
6768
AccountMeta::new_readonly(ctx.accounts.system_program.key(), false),
6869
];
6970
accounts.extend_from_slice(
@@ -86,7 +87,24 @@ impl<'info> AddPrice<'info> {
8687
};
8788
let account_infos = &mut ctx.accounts.to_account_infos();
8889
account_infos.extend_from_slice(ctx.remaining_accounts);
89-
anchor_lang::solana_program::program::invoke(&create_inputs_ix, account_infos)?;
90+
// using find_program_address here instead of ctx.bumps.get since
91+
// that won't be available in the oracle program
92+
let (_, bump) = Pubkey::find_program_address(
93+
&[
94+
ctx.accounts.accumulator_program.key().as_ref(),
95+
CPI.as_bytes(),
96+
],
97+
&crate::ID,
98+
);
99+
anchor_lang::solana_program::program::invoke_signed(
100+
&create_inputs_ix,
101+
account_infos,
102+
&[&[
103+
ctx.accounts.accumulator_program.key().as_ref(),
104+
CPI.as_bytes(),
105+
&[bump],
106+
]],
107+
)?;
90108
Ok(())
91109
}
92110
}
@@ -119,9 +137,14 @@ pub struct AddPrice<'info> {
119137
pub system_program: Program<'info, System>,
120138
/// CHECK: whitelist
121139
pub accumulator_whitelist: UncheckedAccount<'info>,
122-
/// CHECK: instructions introspection sysvar
123-
#[account(address = sysvar::instructions::ID)]
124-
pub ixs_sysvar: UncheckedAccount<'info>,
140+
/// PDA representing this program's authority
141+
/// to call the accumulator program
142+
#[account(
143+
seeds = [accumulator_program.key().as_ref(), b"cpi".as_ref()],
144+
owner = system_program::System::id(),
145+
bump,
146+
)]
147+
pub auth: SystemAccount<'info>,
125148
pub accumulator_program: Program<'info, AccumulatorUpdaterProgram>,
126149
// Remaining Accounts
127150
// should all be new uninitialized accounts

accumulator_updater/programs/mock-cpi-caller/src/instructions/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ pub fn sighash(namespace: &str, name: &str) -> [u8; 8] {
2424
}
2525

2626
pub const ACCUMULATOR_UPDATER_IX_NAME: &str = "put_all";
27+
pub const CPI: &str = "cpi";

accumulator_updater/programs/mock-cpi-caller/src/instructions/update_price.rs

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use {
33
instructions::{
44
sighash,
55
ACCUMULATOR_UPDATER_IX_NAME,
6+
CPI,
67
},
78
message::{
89
price::{
@@ -16,7 +17,7 @@ use {
1617
accumulator_updater::program::AccumulatorUpdater as AccumulatorUpdaterProgram,
1718
anchor_lang::{
1819
prelude::*,
19-
solana_program::sysvar,
20+
system_program,
2021
},
2122
};
2223

@@ -47,9 +48,12 @@ pub struct UpdatePrice<'info> {
4748
pub system_program: Program<'info, System>,
4849
/// CHECK: whitelist
4950
pub accumulator_whitelist: UncheckedAccount<'info>,
50-
/// CHECK: instructions introspection sysvar
51-
#[account(address = sysvar::instructions::ID)]
52-
pub ixs_sysvar: UncheckedAccount<'info>,
51+
#[account(
52+
seeds = [accumulator_program.key().as_ref(), b"cpi".as_ref()],
53+
owner = system_program::System::id(),
54+
bump,
55+
)]
56+
pub auth: SystemAccount<'info>,
5357
pub accumulator_program: Program<'info, AccumulatorUpdaterProgram>,
5458
}
5559

@@ -88,7 +92,7 @@ impl<'info> UpdatePrice<'info> {
8892
let mut accounts = vec![
8993
AccountMeta::new(ctx.accounts.fund.key(), false),
9094
AccountMeta::new_readonly(ctx.accounts.accumulator_whitelist.key(), false),
91-
AccountMeta::new_readonly(ctx.accounts.ixs_sysvar.key(), false),
95+
AccountMeta::new_readonly(ctx.accounts.auth.key(), true),
9296
AccountMeta::new_readonly(ctx.accounts.system_program.key(), false),
9397
];
9498
accounts.extend_from_slice(
@@ -111,7 +115,24 @@ impl<'info> UpdatePrice<'info> {
111115
};
112116
let account_infos = &mut ctx.accounts.to_account_infos();
113117
account_infos.extend_from_slice(ctx.remaining_accounts);
114-
anchor_lang::solana_program::program::invoke(&update_inputs_ix, account_infos)?;
118+
// using find_program_address here instead of ctx.bumps.get since
119+
// that won't be available in the oracle program
120+
let (_, bump) = Pubkey::find_program_address(
121+
&[
122+
ctx.accounts.accumulator_program.key().as_ref(),
123+
CPI.as_bytes(),
124+
],
125+
&crate::ID,
126+
);
127+
anchor_lang::solana_program::program::invoke_signed(
128+
&update_inputs_ix,
129+
account_infos,
130+
&[&[
131+
ctx.accounts.accumulator_program.key().as_ref(),
132+
CPI.as_bytes(),
133+
&[bump],
134+
]],
135+
)?;
115136
Ok(())
116137
}
117138
}

accumulator_updater/tests/accumulator_updater.ts

Lines changed: 34 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ const accumulatorUpdaterProgram = anchor.workspace
1515
.AccumulatorUpdater as Program<AccumulatorUpdater>;
1616
const mockCpiProg = anchor.workspace.MockCpiCaller as Program<MockCpiCaller>;
1717
let whitelistAuthority = anchor.web3.Keypair.generate();
18+
const [mockCpiCallerAuth] = anchor.web3.PublicKey.findProgramAddressSync(
19+
[accumulatorUpdaterProgram.programId.toBuffer(), Buffer.from("cpi")],
20+
mockCpiProg.programId
21+
);
1822
const [fundPda] = anchor.web3.PublicKey.findProgramAddressSync(
1923
[Buffer.from("fund")],
2024
accumulatorUpdaterProgram.programId
@@ -37,6 +41,15 @@ const [pythPriceAccountPk] = anchor.web3.PublicKey.findProgramAddressSync(
3741
mockCpiProg.programId
3842
);
3943

44+
const [accumulatorPdaKey] = anchor.web3.PublicKey.findProgramAddressSync(
45+
[
46+
mockCpiCallerAuth.toBuffer(),
47+
Buffer.from("accumulator"),
48+
pythPriceAccountPk.toBuffer(),
49+
],
50+
accumulatorUpdaterProgram.programId
51+
);
52+
4053
let fundBalance = 100 * anchor.web3.LAMPORTS_PER_SOL;
4154
describe("accumulator_updater", () => {
4255
// Configure the client to use the local cluster.
@@ -70,9 +83,9 @@ describe("accumulator_updater", () => {
7083
});
7184

7285
it("Sets allowed programs to the whitelist", async () => {
73-
const allowedPrograms = [mockCpiProg.programId];
86+
const allowedProgramAuthorities = [mockCpiCallerAuth];
7487
await accumulatorUpdaterProgram.methods
75-
.setAllowedPrograms(allowedPrograms)
88+
.setAllowedPrograms(allowedProgramAuthorities)
7689
.accounts({
7790
authority: whitelistAuthority.publicKey,
7891
})
@@ -87,7 +100,7 @@ describe("accumulator_updater", () => {
87100
);
88101
assert.deepEqual(
89102
whitelistAllowedPrograms,
90-
allowedPrograms.map((p) => p.toString())
103+
allowedProgramAuthorities.map((p) => p.toString())
91104
);
92105
});
93106

@@ -115,21 +128,12 @@ describe("accumulator_updater", () => {
115128
.accounts({
116129
fund: fundPda,
117130
systemProgram: anchor.web3.SystemProgram.programId,
118-
ixsSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
131+
auth: mockCpiCallerAuth,
119132
accumulatorWhitelist: whitelistPubkey,
120133
accumulatorProgram: accumulatorUpdaterProgram.programId,
121134
})
122135
.pubkeys();
123136

124-
const accumulatorPdaKey = anchor.web3.PublicKey.findProgramAddressSync(
125-
[
126-
mockCpiProg.programId.toBuffer(),
127-
Buffer.from("accumulator"),
128-
pythPriceAccountPk.toBuffer(),
129-
],
130-
accumulatorUpdaterProgram.programId
131-
)[0];
132-
133137
const accumulatorPdaMetas = [
134138
{
135139
pubkey: accumulatorPdaKey,
@@ -182,10 +186,6 @@ describe("accumulator_updater", () => {
182186
const pythPriceAccount = await provider.connection.getAccountInfo(
183187
mockCpiCallerAddPriceTxPubkeys.pythPriceAccount
184188
);
185-
const pythPriceAcct = {
186-
...pythPriceAccount,
187-
data: pythPriceAccount.data.toString("hex"),
188-
};
189189

190190
const accumulatorInput =
191191
await accumulatorUpdaterProgram.account.accumulatorInput.fetch(
@@ -242,13 +242,16 @@ describe("accumulator_updater", () => {
242242
emaExpo: new anchor.BN(8),
243243
};
244244

245-
let accumulatorPdaMeta = getAccumulatorPdaMeta(pythPriceAccountPk);
245+
let accumulatorPdaMeta = getAccumulatorPdaMeta(
246+
mockCpiCallerAuth,
247+
pythPriceAccountPk
248+
);
246249
await mockCpiProg.methods
247250
.updatePrice(updatePriceParams)
248251
.accounts({
249252
fund: fundPda,
250253
pythPriceAccount: pythPriceAccountPk,
251-
ixsSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
254+
auth: mockCpiCallerAuth,
252255
accumulatorWhitelist: whitelistPubkey,
253256
accumulatorProgram: accumulatorUpdaterProgram.programId,
254257
})
@@ -302,13 +305,16 @@ describe("accumulator_updater", () => {
302305
emaExpo: new anchor.BN(10 * i + 8),
303306
};
304307

305-
let accumulatorPdaMeta = getAccumulatorPdaMeta(pythPriceAccountPk);
308+
let accumulatorPdaMeta = getAccumulatorPdaMeta(
309+
mockCpiCallerAuth,
310+
pythPriceAccountPk
311+
);
306312
await mockCpiProg.methods
307313
.cpiMaxTest(updatePriceParams, testCase)
308314
.accounts({
309315
fund: fundPda,
310316
pythPriceAccount: pythPriceAccountPk,
311-
ixsSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
317+
auth: mockCpiCallerAuth,
312318
accumulatorWhitelist: whitelistPubkey,
313319
accumulatorProgram: accumulatorUpdaterProgram.programId,
314320
})
@@ -363,15 +369,18 @@ describe("accumulator_updater", () => {
363369
emaExpo: new anchor.BN(10 * i + 8),
364370
};
365371

366-
let accumulatorPdaMeta = getAccumulatorPdaMeta(pythPriceAccountPk);
372+
let accumulatorPdaMeta = getAccumulatorPdaMeta(
373+
mockCpiCallerAuth,
374+
pythPriceAccountPk
375+
);
367376
let errorThrown = false;
368377
try {
369378
await mockCpiProg.methods
370379
.cpiMaxTest(updatePriceParams, testCase)
371380
.accounts({
372381
fund: fundPda,
373382
pythPriceAccount: pythPriceAccountPk,
374-
ixsSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
383+
auth: mockCpiCallerAuth,
375384
accumulatorWhitelist: whitelistPubkey,
376385
accumulatorProgram: accumulatorUpdaterProgram.programId,
377386
})
@@ -391,11 +400,12 @@ describe("accumulator_updater", () => {
391400
});
392401

393402
export const getAccumulatorPdaMeta = (
403+
cpiCallerAuth: anchor.web3.PublicKey,
394404
pythAccount: anchor.web3.PublicKey
395405
): AccountMeta => {
396406
const accumulatorPdaKey = anchor.web3.PublicKey.findProgramAddressSync(
397407
[
398-
mockCpiProg.programId.toBuffer(),
408+
cpiCallerAuth.toBuffer(),
399409
Buffer.from("accumulator"),
400410
pythAccount.toBuffer(),
401411
],

0 commit comments

Comments
 (0)