Skip to content

Commit cb44e15

Browse files
authored
[accumulator-updater 2/x] Manual Serialization & Zero-copy for Mock-cpi-program (#729)
* feat(accumulator_updater): initial skeleton for accumulator_udpater program Initial layout for accumulator updater program. Includes mock-cpi-caller which is meant to represent pyth oracle(or any future allowed program) that will call the accumulator updater program. All implementation details are open for discussion/subject to change * test(accumulator_updater): add additional checks in tests and minor clean up * chore(accumulator_updater): misc clean-up * refactor(accumulator_updater): added comments & to-dos and minor refactoring to address PR comments * feat(accumulator_updater): nmanual serialization for mock-cpi-caller schemas & use zero-copy * chore(accumulator-updater): misc clean-up * refactor(accumulator-updater): rename parameter in accumulator-updater::initalize ix for consistency * style(accumulator-updater): switch PriceAccountType enum variants to camelcase * refactor(accumulator-updater): address PR comments rename schema to message & associated price messages, remove unncessary comments, changed addAllowedProgram to setAllowedPrograms * refactor(accumulator-updater): address more PR comments consolidate SetAllowedPrograms and UpdateWhitelistAuthority into one context * style(accumulator-updater): minor style fixes to address PR comments
1 parent 2db5a26 commit cb44e15

File tree

7 files changed

+327
-128
lines changed

7 files changed

+327
-128
lines changed

accumulator_updater/Cargo.lock

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

accumulator_updater/programs/accumulator_updater/src/lib.rs

Lines changed: 64 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,34 +18,43 @@ declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
1818
pub mod accumulator_updater {
1919
use super::*;
2020

21-
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
21+
pub fn initialize(ctx: Context<Initialize>, authority: Pubkey) -> Result<()> {
22+
require_keys_neq!(authority, Pubkey::default());
2223
let whitelist = &mut ctx.accounts.whitelist;
2324
whitelist.bump = *ctx.bumps.get("whitelist").unwrap();
25+
whitelist.authority = authority;
2426
Ok(())
2527
}
2628

27-
//TODO: add authorization mechanism for this
28-
pub fn add_allowed_program(
29-
ctx: Context<AddAllowedProgram>,
30-
allowed_program: Pubkey,
29+
pub fn set_allowed_programs(
30+
ctx: Context<UpdateWhitelist>,
31+
allowed_programs: Vec<Pubkey>,
3132
) -> Result<()> {
3233
let whitelist = &mut ctx.accounts.whitelist;
33-
require_keys_neq!(allowed_program, Pubkey::default());
34-
require!(
35-
!whitelist.allowed_programs.contains(&allowed_program),
36-
AccumulatorUpdaterError::DuplicateAllowedProgram
37-
);
38-
whitelist.allowed_programs.push(allowed_program);
34+
whitelist.validate_programs(&allowed_programs)?;
35+
whitelist.allowed_programs = allowed_programs;
36+
Ok(())
37+
}
38+
39+
pub fn update_whitelist_authority(
40+
ctx: Context<UpdateWhitelist>,
41+
new_authority: Pubkey,
42+
) -> Result<()> {
43+
let whitelist = &mut ctx.accounts.whitelist;
44+
whitelist.validate_new_authority(new_authority)?;
45+
whitelist.authority = new_authority;
3946
Ok(())
4047
}
4148

4249
/// Add new account(s) to be included in the accumulator
4350
///
44-
/// * `base_account` - Pubkey of the original account the AccumulatorInput(s) are derived from
45-
/// * `data` - Vec of AccumulatorInput account data
46-
/// * `account_type` - Marker to indicate base_account account_type
47-
/// * `account_schemas` - Vec of markers to indicate schemas for AccumulatorInputs. In same respective
48-
/// order as data
51+
/// * `base_account` - Pubkey of the original account the
52+
/// AccumulatorInput(s) are derived from
53+
/// * `data` - Vec of AccumulatorInput account data
54+
/// * `account_type` - Marker to indicate base_account account_type
55+
/// * `account_schemas` - Vec of markers to indicate schemas for
56+
/// AccumulatorInputs. In same respective
57+
/// order as data
4958
pub fn create_inputs<'info>(
5059
ctx: Context<'_, '_, '_, 'info, CreateInputs<'info>>,
5160
base_account: Pubkey,
@@ -105,10 +114,31 @@ pub mod accumulator_updater {
105114
#[derive(InitSpace)]
106115
pub struct Whitelist {
107116
pub bump: u8,
117+
pub authority: Pubkey,
108118
#[max_len(32)]
109119
pub allowed_programs: Vec<Pubkey>,
110120
}
111121

122+
impl Whitelist {
123+
pub fn validate_programs(&self, allowed_programs: &[Pubkey]) -> Result<()> {
124+
require!(
125+
!self.allowed_programs.contains(&Pubkey::default()),
126+
AccumulatorUpdaterError::InvalidAllowedProgram
127+
);
128+
require_gte!(
129+
32,
130+
allowed_programs.len(),
131+
AccumulatorUpdaterError::MaximumAllowedProgramsExceeded
132+
);
133+
Ok(())
134+
}
135+
136+
pub fn validate_new_authority(&self, new_authority: Pubkey) -> Result<()> {
137+
require_keys_neq!(new_authority, Pubkey::default());
138+
Ok(())
139+
}
140+
}
141+
112142

113143
#[derive(Accounts)]
114144
pub struct WhitelistVerifier<'info> {
@@ -141,7 +171,8 @@ impl<'info> WhitelistVerifier<'info> {
141171
#[derive(Accounts)]
142172
pub struct Initialize<'info> {
143173
#[account(mut)]
144-
pub payer: Signer<'info>,
174+
pub payer: Signer<'info>,
175+
145176
#[account(
146177
init,
147178
payer = payer,
@@ -153,17 +184,20 @@ pub struct Initialize<'info> {
153184
pub system_program: Program<'info, System>,
154185
}
155186

187+
156188
#[derive(Accounts)]
157-
pub struct AddAllowedProgram<'info> {
189+
pub struct UpdateWhitelist<'info> {
158190
#[account(mut)]
159-
pub payer: Signer<'info>,
191+
pub payer: Signer<'info>,
192+
193+
pub authority: Signer<'info>,
160194
#[account(
161-
mut,
162-
seeds = [b"accumulator".as_ref(), b"whitelist".as_ref()],
163-
bump = whitelist.bump,
195+
mut,
196+
seeds = [b"accumulator".as_ref(), b"whitelist".as_ref()],
197+
bump = whitelist.bump,
198+
has_one = authority
164199
)]
165-
pub whitelist: Account<'info, Whitelist>,
166-
pub system_program: Program<'info, System>,
200+
pub whitelist: Account<'info, Whitelist>,
167201
}
168202

169203

@@ -294,4 +328,10 @@ pub enum AccumulatorUpdaterError {
294328
ConversionError,
295329
#[msg("Serialization Error")]
296330
SerializeError,
331+
#[msg("Whitelist admin required on initialization")]
332+
WhitelistAdminRequired,
333+
#[msg("Invalid allowed program")]
334+
InvalidAllowedProgram,
335+
#[msg("Maximum number of allowed programs exceeded")]
336+
MaximumAllowedProgramsExceeded,
297337
}

accumulator_updater/programs/mock-cpi-caller/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ default = []
1818
[dependencies]
1919
anchor-lang = "0.27.0"
2020
accumulator_updater = { path = "../accumulator_updater", features = ["cpi"] }
21+
# needed for the new #[account(zero_copy)] in anchor 0.27.0
22+
bytemuck = { version = "1.4.0", features = ["derive", "min_const_generics"]}

accumulator_updater/programs/mock-cpi-caller/src/lib.rs

Lines changed: 36 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@ use {
1010
sysvar,
1111
},
1212
},
13+
message::{
14+
get_schemas,
15+
price::*,
16+
AccumulatorSerializer,
17+
},
1318
};
1419

20+
pub mod message;
1521
declare_id!("Dg5PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
1622

1723
#[program]
@@ -22,26 +28,27 @@ pub mod mock_cpi_caller {
2228
ctx: Context<'_, '_, '_, 'info, AddPrice<'info>>,
2329
params: AddPriceParams,
2430
) -> Result<()> {
25-
let pyth_price_acct = &mut ctx.accounts.pyth_price_account;
26-
pyth_price_acct.init(params)?;
31+
let mut account_data: Vec<Vec<u8>> = vec![];
32+
let schemas = get_schemas(PythAccountType::Price);
2733

28-
let mut price_account_data_vec = vec![];
29-
AccountSerialize::try_serialize(
30-
&pyth_price_acct.clone().into_inner(),
31-
&mut price_account_data_vec,
32-
)?;
34+
{
35+
let pyth_price_acct = &mut ctx.accounts.pyth_price_account.load_init()?;
36+
37+
pyth_price_acct.init(params)?;
38+
39+
let price_full_data =
40+
FullPriceMessage::from(&**pyth_price_acct).accumulator_serialize()?;
3341

42+
account_data.push(price_full_data);
3443

35-
let price_only_data = PriceOnly::from(&pyth_price_acct.clone().into_inner())
36-
.try_to_vec()
37-
.unwrap();
44+
45+
let price_compact_data =
46+
CompactPriceMessage::from(&**pyth_price_acct).accumulator_serialize()?;
47+
account_data.push(price_compact_data);
48+
}
3849

3950

40-
let account_data: Vec<Vec<u8>> = vec![price_account_data_vec, price_only_data];
41-
let account_schemas = [PythSchemas::Full, PythSchemas::Compact]
42-
.into_iter()
43-
.map(|s| s.to_u8())
44-
.collect::<Vec<u8>>();
51+
let account_schemas = schemas.into_iter().map(|s| s.to_u8()).collect::<Vec<u8>>();
4552

4653
// 44444 compute units
4754
// AddPrice::invoke_cpi_anchor(ctx, account_data, PythAccountType::Price, account_schemas)
@@ -81,7 +88,6 @@ impl<'info> AddPrice<'info> {
8188
account_schemas: Vec<u8>,
8289
) -> Result<()> {
8390
accumulator_updater::cpi::create_inputs(
84-
// cpi_ctx,
8591
ctx.accounts.create_inputs_ctx(ctx.remaining_accounts),
8692
ctx.accounts.pyth_price_account.key(),
8793
account_data,
@@ -111,7 +117,7 @@ impl<'info> AddPrice<'info> {
111117
.map(|a| AccountMeta::new(a.key(), false))
112118
.collect::<Vec<_>>(),
113119
);
114-
let add_accumulator_input_ix = anchor_lang::solana_program::instruction::Instruction {
120+
let create_inputs_ix = anchor_lang::solana_program::instruction::Instruction {
115121
program_id: ctx.accounts.accumulator_program.key(),
116122
accounts,
117123
data: (
@@ -127,7 +133,7 @@ impl<'info> AddPrice<'info> {
127133
};
128134
let account_infos = &mut ctx.accounts.to_account_infos();
129135
account_infos.extend_from_slice(ctx.remaining_accounts);
130-
anchor_lang::solana_program::program::invoke(&add_accumulator_input_ix, account_infos)?;
136+
anchor_lang::solana_program::program::invoke(&create_inputs_ix, account_infos)?;
131137
Ok(())
132138
}
133139
}
@@ -153,6 +159,13 @@ pub struct AddPriceParams {
153159
pub ema_expo: u64,
154160
}
155161

162+
trait PythAccount {
163+
const ACCOUNT_TYPE: PythAccountType;
164+
fn account_type() -> PythAccountType {
165+
Self::ACCOUNT_TYPE
166+
}
167+
}
168+
156169
#[derive(Copy, Clone)]
157170
#[repr(u32)]
158171
pub enum PythAccountType {
@@ -168,20 +181,6 @@ impl PythAccountType {
168181
}
169182
}
170183

171-
#[derive(Copy, Clone)]
172-
#[repr(u8)]
173-
pub enum PythSchemas {
174-
Full = 0,
175-
Compact = 1,
176-
Minimal = 2,
177-
}
178-
179-
impl PythSchemas {
180-
fn to_u8(&self) -> u8 {
181-
*self as u8
182-
}
183-
}
184-
185184
#[derive(Accounts)]
186185
#[instruction(params: AddPriceParams)]
187186
pub struct AddPrice<'info> {
@@ -192,7 +191,7 @@ pub struct AddPrice<'info> {
192191
bump,
193192
space = 8 + PriceAccount::INIT_SPACE
194193
)]
195-
pub pyth_price_account: Account<'info, PriceAccount>,
194+
pub pyth_price_account: AccountLoader<'info, PriceAccount>,
196195
#[account(mut)]
197196
pub payer: Signer<'info>,
198197
/// also needed for accumulator_updater
@@ -208,15 +207,15 @@ pub struct AddPrice<'info> {
208207
}
209208

210209

211-
//Note: this will use anchor's default borsh serialization schema with the header
212-
#[account]
210+
#[account(zero_copy)]
213211
#[derive(InitSpace)]
214212
pub struct PriceAccount {
215213
pub id: u64,
216214
pub price: u64,
217215
pub price_expo: u64,
218216
pub ema: u64,
219217
pub ema_expo: u64,
218+
pub comp_: [Pubkey; 32],
220219
}
221220

222221
impl PriceAccount {
@@ -230,46 +229,11 @@ impl PriceAccount {
230229
}
231230
}
232231

233-
// #[derive(Default, Debug, borsh::BorshSerialize)]
234-
#[derive(AnchorSerialize, AnchorDeserialize, Default, Debug, Clone)]
235-
pub struct PriceOnly {
236-
pub price_expo: u64,
237-
pub price: u64,
238-
pub id: u64,
239-
}
240-
241-
impl PriceOnly {
242-
fn serialize(&self) -> Vec<u8> {
243-
self.try_to_vec().unwrap()
244-
}
245-
246-
fn serialize_from_price_account(other: PriceAccount) -> Vec<u8> {
247-
PriceOnly::from(&other).try_to_vec().unwrap()
248-
}
249-
}
250-
251-
252-
impl From<&PriceAccount> for PriceOnly {
253-
fn from(other: &PriceAccount) -> Self {
254-
Self {
255-
id: other.id,
256-
price: other.price,
257-
price_expo: other.price_expo,
258-
}
259-
}
232+
impl PythAccount for PriceAccount {
233+
const ACCOUNT_TYPE: PythAccountType = PythAccountType::Price;
260234
}
261235

262236

263-
impl From<PriceAccount> for PriceOnly {
264-
fn from(other: PriceAccount) -> Self {
265-
Self {
266-
id: other.id,
267-
price: other.price,
268-
price_expo: other.price_expo,
269-
}
270-
}
271-
}
272-
273237
#[cfg(test)]
274238
mod test {
275239
use {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use crate::PythAccountType;
2+
3+
pub mod price;
4+
5+
#[derive(Copy, Clone)]
6+
#[repr(u8)]
7+
pub enum MessageSchema {
8+
Full = 0,
9+
Compact = 1,
10+
Minimal = 2,
11+
}
12+
13+
impl MessageSchema {
14+
pub fn to_u8(&self) -> u8 {
15+
*self as u8
16+
}
17+
}
18+
19+
20+
pub fn get_schemas(account_type: PythAccountType) -> Vec<MessageSchema> {
21+
match account_type {
22+
PythAccountType::Price => vec![MessageSchema::Full, MessageSchema::Compact],
23+
_ => vec![MessageSchema::Full],
24+
}
25+
}
26+
27+
pub trait AccumulatorSerializer {
28+
fn accumulator_serialize(&self) -> anchor_lang::Result<Vec<u8>>;
29+
}

0 commit comments

Comments
 (0)