Skip to content

Commit 7b0851f

Browse files
authored
[accumulator-updater 7/x] - cpi max limit (#772)
* feat(accumulator-updater): add funding account for AccumulatorInput creation * feat(accumulator-updater): add ix for testing cpi max size add steps to notes.md on how to test against solana-test-validator with features deactivated * test(accumulator-updater): fix max num of msgs to send * refactor(accumulator-updater): address PR feedback from 771 update fund pda seeds, update consts, cleanup * feat(accumulator-updater): address PR comments Update cpi max test ix to take vec of msg sizes, clean up commented out code
1 parent 259f012 commit 7b0851f

File tree

10 files changed

+216
-25
lines changed

10 files changed

+216
-25
lines changed

accumulator_updater/NOTES.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,29 @@
1+
## Testing
2+
3+
- run `anchor test` if no special customization for the test-validator is needed
4+
- `anchor test` will run `solana-test-validator` will all features activated.
5+
One of the features activated on the test-validator which is not currently activated on pythnet is
6+
7+
```
8+
"GDH5TVdbTPUpRnXaRyQqiKUa7uZAbZ28Q2N9bhbKoMLm loosen cpi size restrictions #26641"
9+
```
10+
11+
In order to run `solana-test-validator` with this feature deactivated, do the following:
12+
13+
1. open a terminal and run `solana-test-validator --reset --deactivate-feature GDH5TVdbTPUpRnXaRyQqiKUa7uZAbZ28Q2N9bhbKoMLm`
14+
2. open a separate terminal and run `anchor build` in the `accumulator_updater` dir
15+
3. get the pubkeys of the program keypairs `solana address -k accumulator_updater/target/deploy/<program_keypair>.json`
16+
4. change the pubkeys in the `declare_id!` macro to these keypairs
17+
5. update `Anchor.toml` `[programs.localnet]` programIds as well
18+
6. run `anchor test --skip-local-validator`
19+
120
## Questions
221

322
1. Do we need to support multiple Whitelists?
423
2. Support multiple accumulators
524
1. should each accumulator store a different type of data?
625
=> implications for length of merkle proof
7-
2.
8-
3. authority?
9-
4. how to know what went into the `AccumulatorAccount` (for deserializing/proofs)
26+
3. how to know what went into the `AccumulatorAccount` (for deserializing/proofs)
1027
1. Header?
1128

1229
## To Do

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

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ use {
1313
};
1414

1515

16-
pub const ACCUMULATOR: &[u8; 11] = b"accumulator";
17-
pub const FUND: &[u8; 4] = b"fund";
16+
pub const ACCUMULATOR: &str = "accumulator";
17+
pub const FUND: &str = "fund";
18+
1819

1920
pub fn put_all<'info>(
2021
ctx: Context<'_, '_, '_, 'info, PutAll<'info>>,
@@ -34,23 +35,23 @@ pub fn put_all<'info>(
3435
let (pda, bump) = Pubkey::find_program_address(
3536
&[
3637
cpi_caller.as_ref(),
37-
ACCUMULATOR.as_ref(),
38+
ACCUMULATOR.as_bytes(),
3839
base_account_key.as_ref(),
3940
],
4041
&crate::ID,
4142
);
4243
require_keys_eq!(accumulator_input_ai.key(), pda);
4344
let signer_seeds = [
4445
cpi_caller.as_ref(),
45-
ACCUMULATOR.as_ref(),
46+
ACCUMULATOR.as_bytes(),
4647
base_account_key.as_ref(),
4748
&[bump],
4849
];
4950
let fund_pda_bump = *ctx
5051
.bumps
51-
.get("fund")
52+
.get(FUND)
5253
.ok_or(AccumulatorUpdaterError::FundBumpNotFound)?;
53-
let fund_signer_seeds = [ACCUMULATOR.as_ref(), FUND.as_ref(), &[fund_pda_bump]];
54+
let fund_signer_seeds = [FUND.as_bytes(), &[fund_pda_bump]];
5455
PutAll::create_account(
5556
accumulator_input_ai,
5657
8 + AccumulatorInput::INIT_SPACE,
@@ -102,10 +103,7 @@ pub struct PutAll<'info> {
102103
/// `AccumulatorInput` account initialization
103104
#[account(
104105
mut,
105-
seeds = [
106-
b"accumulator".as_ref(),
107-
b"fund".as_ref(),
108-
],
106+
seeds = [b"fund".as_ref()],
109107
owner = system_program::System::id(),
110108
bump,
111109
)]
@@ -125,7 +123,6 @@ impl<'info> PutAll<'info> {
125123
system_program: &AccountInfo<'a>,
126124
) -> Result<()> {
127125
let lamports = Rent::get()?.minimum_balance(space);
128-
129126
system_program::create_account(
130127
CpiContext::new_with_signer(
131128
system_program.to_account_info(),

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ pub struct WhitelistVerifier<'info> {
4747
seeds = [b"accumulator".as_ref(), b"whitelist".as_ref()],
4848
bump = whitelist.bump,
4949
)]
50-
pub whitelist: Account<'info, Whitelist>,
50+
// Using a Box to move account from stack to heap
51+
pub whitelist: Box<Account<'info, Whitelist>>,
5152
/// CHECK: Instruction introspection sysvar
5253
#[account(address = sysvar::instructions::ID)]
5354
pub ixs_sysvar: UncheckedAccount<'info>,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
use {
2+
crate::{
3+
instructions::{
4+
UpdatePrice,
5+
UpdatePriceParams,
6+
},
7+
message::{
8+
price::DummyPriceMessage,
9+
AccumulatorSerializer,
10+
},
11+
},
12+
anchor_lang::prelude::*,
13+
};
14+
15+
pub fn cpi_max_test<'info>(
16+
ctx: Context<'_, '_, '_, 'info, UpdatePrice<'info>>,
17+
params: UpdatePriceParams,
18+
msg_sizes: Vec<u16>,
19+
) -> Result<()> {
20+
let mut inputs = vec![];
21+
22+
{
23+
let pyth_price_acct = &mut ctx.accounts.pyth_price_account.load_mut()?;
24+
pyth_price_acct.update(params)?;
25+
26+
for msg_size in msg_sizes {
27+
let price_dummy_data = DummyPriceMessage::new(msg_size).accumulator_serialize()?;
28+
inputs.push(price_dummy_data);
29+
}
30+
}
31+
32+
let input_len = inputs.iter().map(|x| x.len()).sum::<usize>();
33+
msg!("input_len: {}", input_len);
34+
35+
36+
UpdatePrice::emit_accumulator_inputs(ctx, inputs)
37+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use anchor_lang::solana_program::hash::hashv;
22
pub use {
33
add_price::*,
4+
cpi_max_test::*,
45
update_price::*,
56
};
7+
68
mod add_price;
9+
mod cpi_max_test;
710
mod update_price;
811

912
/// Generate discriminator to be able to call anchor program's ix

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

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,13 @@ use {
55
ACCUMULATOR_UPDATER_IX_NAME,
66
},
77
message::{
8-
get_schemas,
98
price::{
109
CompactPriceMessage,
1110
FullPriceMessage,
1211
},
1312
AccumulatorSerializer,
1413
},
15-
state::{
16-
PriceAccount,
17-
PythAccountType,
18-
},
14+
state::PriceAccount,
1915
},
2016
accumulator_updater::program::AccumulatorUpdater as AccumulatorUpdaterProgram,
2117
anchor_lang::{
@@ -45,8 +41,6 @@ pub struct UpdatePrice<'info> {
4541
bump,
4642
)]
4743
pub pyth_price_account: AccountLoader<'info, PriceAccount>,
48-
// #[account(mut)]
49-
// pub payer: Signer<'info>,
5044
#[account(mut)]
5145
pub fund: SystemAccount<'info>,
5246
/// Needed for accumulator_updater
@@ -66,7 +60,6 @@ pub fn update_price<'info>(
6660
params: UpdatePriceParams,
6761
) -> Result<()> {
6862
let mut inputs = vec![];
69-
let _schemas = get_schemas(PythAccountType::Price);
7063

7164
{
7265
let pyth_price_acct = &mut ctx.accounts.pyth_price_account.load_mut()?;

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ pub mod mock_cpi_caller {
2828
) -> Result<()> {
2929
instructions::update_price(ctx, params)
3030
}
31+
32+
/// num_messages is the number of 1kb messages to send to the CPI
33+
pub fn cpi_max_test<'info>(
34+
ctx: Context<'_, '_, '_, 'info, UpdatePrice<'info>>,
35+
params: UpdatePriceParams,
36+
msg_sizes: Vec<u16>,
37+
) -> Result<()> {
38+
instructions::cpi_max_test(ctx, params, msg_sizes)
39+
}
3140
}
3241

3342

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub enum MessageSchema {
88
Full = 0,
99
Compact = 1,
1010
Minimal = 2,
11+
Dummy = 3,
1112
}
1213

1314
impl MessageSchema {

accumulator_updater/programs/mock-cpi-caller/src/message/price.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,35 @@ impl AccumulatorSerializer for FullPriceMessage {
113113
Ok(bytes)
114114
}
115115
}
116+
117+
118+
#[repr(C)]
119+
#[derive(Clone, Debug, Eq, PartialEq)]
120+
pub struct DummyPriceMessage {
121+
pub header: MessageHeader,
122+
pub data: Vec<u8>,
123+
}
124+
125+
126+
impl DummyPriceMessage {
127+
pub const SIZE: usize = 1017;
128+
129+
pub fn new(msg_size: u16) -> Self {
130+
Self {
131+
header: MessageHeader::new(MessageSchema::Dummy, msg_size as u32),
132+
data: vec![0u8; msg_size as usize],
133+
}
134+
}
135+
}
136+
137+
138+
impl AccumulatorSerializer for DummyPriceMessage {
139+
fn accumulator_serialize(&self) -> Result<Vec<u8>> {
140+
let mut bytes = vec![];
141+
bytes.write_all(&self.header.schema.to_be_bytes())?;
142+
bytes.write_all(&self.header.version.to_be_bytes())?;
143+
bytes.write_all(&self.header.size.to_be_bytes())?;
144+
bytes.extend_from_slice(&self.data);
145+
Ok(bytes)
146+
}
147+
}

accumulator_updater/tests/accumulator_updater.ts

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const accumulatorUpdaterProgram = anchor.workspace
1616
const mockCpiProg = anchor.workspace.MockCpiCaller as Program<MockCpiCaller>;
1717
let whitelistAuthority = anchor.web3.Keypair.generate();
1818
const [fundPda] = anchor.web3.PublicKey.findProgramAddressSync(
19-
[Buffer.from("accumulator"), Buffer.from("fund")],
19+
[Buffer.from("fund")],
2020
accumulatorUpdaterProgram.programId
2121
);
2222

@@ -287,6 +287,107 @@ describe("accumulator_updater", () => {
287287
assert.isTrue(pm.priceExpo.eq(updatePriceParams.priceExpo));
288288
});
289289
});
290+
291+
it("Mock CPI Program - CPI Max Test", async () => {
292+
// with loosen CPI feature activated, max cpi instruction size len is 10KB
293+
let testCases = [[1024], [1024, 2048], [1024, 2048, 4096]];
294+
// for (let i = 1; i < 8; i++) {
295+
for (let i = 0; i < testCases.length; i++) {
296+
let testCase = testCases[i];
297+
console.info(`testCase: ${testCase}`);
298+
const updatePriceParams = {
299+
price: new anchor.BN(10 * i + 5),
300+
priceExpo: new anchor.BN(10 & (i + 6)),
301+
ema: new anchor.BN(10 * i + 7),
302+
emaExpo: new anchor.BN(10 * i + 8),
303+
};
304+
305+
let accumulatorPdaMeta = getAccumulatorPdaMeta(pythPriceAccountPk);
306+
await mockCpiProg.methods
307+
.cpiMaxTest(updatePriceParams, testCase)
308+
.accounts({
309+
fund: fundPda,
310+
pythPriceAccount: pythPriceAccountPk,
311+
ixsSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
312+
accumulatorWhitelist: whitelistPubkey,
313+
accumulatorProgram: accumulatorUpdaterProgram.programId,
314+
})
315+
.remainingAccounts([accumulatorPdaMeta])
316+
.preInstructions([
317+
ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
318+
])
319+
.rpc({
320+
skipPreflight: true,
321+
});
322+
323+
const pythPriceAccount = await mockCpiProg.account.priceAccount.fetch(
324+
pythPriceAccountPk
325+
);
326+
assert.isTrue(pythPriceAccount.price.eq(updatePriceParams.price));
327+
assert.isTrue(pythPriceAccount.priceExpo.eq(updatePriceParams.priceExpo));
328+
assert.isTrue(pythPriceAccount.ema.eq(updatePriceParams.ema));
329+
assert.isTrue(pythPriceAccount.emaExpo.eq(updatePriceParams.emaExpo));
330+
const accumulatorInput =
331+
await accumulatorUpdaterProgram.account.accumulatorInput.fetch(
332+
accumulatorPdaMeta.pubkey
333+
);
334+
const updatedAccumulatorPriceMessages =
335+
parseAccumulatorInput(accumulatorInput);
336+
337+
console.log(
338+
`updatedAccumulatorPriceMessages: ${JSON.stringify(
339+
updatedAccumulatorPriceMessages,
340+
null,
341+
2
342+
)}`
343+
);
344+
updatedAccumulatorPriceMessages.forEach((pm) => {
345+
assert.isTrue(pm.id.eq(addPriceParams.id));
346+
assert.isTrue(pm.price.eq(updatePriceParams.price));
347+
assert.isTrue(pm.priceExpo.eq(updatePriceParams.priceExpo));
348+
});
349+
}
350+
});
351+
352+
it("Mock CPI Program - CPI Max Test Fail", async () => {
353+
// with loosen CPI feature activated, max cpi instruction size len is 10KB
354+
let testCases = [[1024, 2048, 4096, 8192]];
355+
// for (let i = 1; i < 8; i++) {
356+
for (let i = 0; i < testCases.length; i++) {
357+
let testCase = testCases[i];
358+
console.info(`testCase: ${testCase}`);
359+
const updatePriceParams = {
360+
price: new anchor.BN(10 * i + 5),
361+
priceExpo: new anchor.BN(10 & (i + 6)),
362+
ema: new anchor.BN(10 * i + 7),
363+
emaExpo: new anchor.BN(10 * i + 8),
364+
};
365+
366+
let accumulatorPdaMeta = getAccumulatorPdaMeta(pythPriceAccountPk);
367+
let errorThrown = false;
368+
try {
369+
await mockCpiProg.methods
370+
.cpiMaxTest(updatePriceParams, testCase)
371+
.accounts({
372+
fund: fundPda,
373+
pythPriceAccount: pythPriceAccountPk,
374+
ixsSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
375+
accumulatorWhitelist: whitelistPubkey,
376+
accumulatorProgram: accumulatorUpdaterProgram.programId,
377+
})
378+
.remainingAccounts([accumulatorPdaMeta])
379+
.preInstructions([
380+
ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
381+
])
382+
.rpc({
383+
skipPreflight: true,
384+
});
385+
} catch (_err) {
386+
errorThrown = true;
387+
}
388+
assert.ok(errorThrown);
389+
}
390+
});
290391
});
291392

292393
export const getAccumulatorPdaMeta = (
@@ -339,7 +440,7 @@ function parseAccumulatorInput({
339440
} else if (msgHeader.schema == 1) {
340441
accumulatorMessages.push(parseCompactPriceMessage(msgData));
341442
} else {
342-
console.warn("Unknown input index: " + i);
443+
console.warn("unknown msgHeader.schema: " + i);
343444
continue;
344445
}
345446
start = endOffset;

0 commit comments

Comments
 (0)