Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions accumulator_updater/NOTES.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
## Testing

- run `anchor test` if no special customization for the test-validator is needed
- `anchor test` will run `solana-test-validator` will all features activated.
One of the features activated on the test-validator which is not currently activated on pythnet is

```
"GDH5TVdbTPUpRnXaRyQqiKUa7uZAbZ28Q2N9bhbKoMLm loosen cpi size restrictions #26641"
```

In order to run `solana-test-validator` with this feature deactivated, do the following:

1. open a terminal and run `solana-test-validator --reset --deactivate-feature GDH5TVdbTPUpRnXaRyQqiKUa7uZAbZ28Q2N9bhbKoMLm`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's remember to set this flag properly in the integration testing framework

2. open a separate terminal and run `anchor build` in the `accumulator_updater` dir
3. get the pubkeys of the program keypairs `solana address -k accumulator_updater/target/deploy/<program_keypair>.json`
4. change the pubkeys in the `declare_id!` macro to these keypairs
5. update `Anchor.toml` `[programs.localnet]` programIds as well
6. run `anchor test --skip-local-validator`

## Questions

1. Do we need to support multiple Whitelists?
2. Support multiple accumulators
1. should each accumulator store a different type of data?
=> implications for length of merkle proof
2.
3. authority?
4. how to know what went into the `AccumulatorAccount` (for deserializing/proofs)
3. how to know what went into the `AccumulatorAccount` (for deserializing/proofs)
1. Header?

## To Do
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ use {
};


pub const ACCUMULATOR: &[u8; 11] = b"accumulator";
pub const FUND: &[u8; 4] = b"fund";
pub const ACCUMULATOR: &str = "accumulator";
pub const FUND: &str = "fund";


pub fn put_all<'info>(
ctx: Context<'_, '_, '_, 'info, PutAll<'info>>,
Expand All @@ -34,23 +35,23 @@ pub fn put_all<'info>(
let (pda, bump) = Pubkey::find_program_address(
&[
cpi_caller.as_ref(),
ACCUMULATOR.as_ref(),
ACCUMULATOR.as_bytes(),
base_account_key.as_ref(),
],
&crate::ID,
);
require_keys_eq!(accumulator_input_ai.key(), pda);
let signer_seeds = [
cpi_caller.as_ref(),
ACCUMULATOR.as_ref(),
ACCUMULATOR.as_bytes(),
base_account_key.as_ref(),
&[bump],
];
let fund_pda_bump = *ctx
.bumps
.get("fund")
.get(FUND)
.ok_or(AccumulatorUpdaterError::FundBumpNotFound)?;
let fund_signer_seeds = [ACCUMULATOR.as_ref(), FUND.as_ref(), &[fund_pda_bump]];
let fund_signer_seeds = [FUND.as_bytes(), &[fund_pda_bump]];
PutAll::create_account(
accumulator_input_ai,
8 + AccumulatorInput::INIT_SPACE,
Expand Down Expand Up @@ -102,10 +103,7 @@ pub struct PutAll<'info> {
/// `AccumulatorInput` account initialization
#[account(
mut,
seeds = [
b"accumulator".as_ref(),
b"fund".as_ref(),
],
seeds = [b"fund".as_ref()],
owner = system_program::System::id(),
bump,
)]
Expand All @@ -125,7 +123,6 @@ impl<'info> PutAll<'info> {
system_program: &AccountInfo<'a>,
) -> Result<()> {
let lamports = Rent::get()?.minimum_balance(space);

system_program::create_account(
CpiContext::new_with_signer(
system_program.to_account_info(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ pub struct WhitelistVerifier<'info> {
seeds = [b"accumulator".as_ref(), b"whitelist".as_ref()],
bump = whitelist.bump,
)]
pub whitelist: Account<'info, Whitelist>,
// Using a Box to move account from stack to heap
pub whitelist: Box<Account<'info, Whitelist>>,
/// CHECK: Instruction introspection sysvar
#[account(address = sysvar::instructions::ID)]
pub ixs_sysvar: UncheckedAccount<'info>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use {
crate::{
instructions::{
UpdatePrice,
UpdatePriceParams,
},
message::{
price::DummyPriceMessage,
AccumulatorSerializer,
},
},
anchor_lang::prelude::*,
};

pub fn cpi_max_test<'info>(
ctx: Context<'_, '_, '_, 'info, UpdatePrice<'info>>,
params: UpdatePriceParams,
msg_sizes: Vec<u16>,
) -> Result<()> {
let mut inputs = vec![];

{
let pyth_price_acct = &mut ctx.accounts.pyth_price_account.load_mut()?;
pyth_price_acct.update(params)?;

for msg_size in msg_sizes {
let price_dummy_data = DummyPriceMessage::new(msg_size).accumulator_serialize()?;
inputs.push(price_dummy_data);
}
}

let input_len = inputs.iter().map(|x| x.len()).sum::<usize>();
msg!("input_len: {}", input_len);


UpdatePrice::emit_accumulator_inputs(ctx, inputs)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
use anchor_lang::solana_program::hash::hashv;
pub use {
add_price::*,
cpi_max_test::*,
update_price::*,
};

mod add_price;
mod cpi_max_test;
mod update_price;

/// Generate discriminator to be able to call anchor program's ix
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,13 @@ use {
ACCUMULATOR_UPDATER_IX_NAME,
},
message::{
get_schemas,
price::{
CompactPriceMessage,
FullPriceMessage,
},
AccumulatorSerializer,
},
state::{
PriceAccount,
PythAccountType,
},
state::PriceAccount,
},
accumulator_updater::program::AccumulatorUpdater as AccumulatorUpdaterProgram,
anchor_lang::{
Expand Down Expand Up @@ -45,8 +41,6 @@ pub struct UpdatePrice<'info> {
bump,
)]
pub pyth_price_account: AccountLoader<'info, PriceAccount>,
// #[account(mut)]
// pub payer: Signer<'info>,
#[account(mut)]
pub fund: SystemAccount<'info>,
/// Needed for accumulator_updater
Expand All @@ -66,7 +60,6 @@ pub fn update_price<'info>(
params: UpdatePriceParams,
) -> Result<()> {
let mut inputs = vec![];
let _schemas = get_schemas(PythAccountType::Price);

{
let pyth_price_acct = &mut ctx.accounts.pyth_price_account.load_mut()?;
Expand Down
9 changes: 9 additions & 0 deletions accumulator_updater/programs/mock-cpi-caller/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ pub mod mock_cpi_caller {
) -> Result<()> {
instructions::update_price(ctx, params)
}

/// num_messages is the number of 1kb messages to send to the CPI
pub fn cpi_max_test<'info>(
ctx: Context<'_, '_, '_, 'info, UpdatePrice<'info>>,
params: UpdatePriceParams,
msg_sizes: Vec<u16>,
) -> Result<()> {
instructions::cpi_max_test(ctx, params, msg_sizes)
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub enum MessageSchema {
Full = 0,
Compact = 1,
Minimal = 2,
Dummy = 3,
}

impl MessageSchema {
Expand Down
32 changes: 32 additions & 0 deletions accumulator_updater/programs/mock-cpi-caller/src/message/price.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,35 @@ impl AccumulatorSerializer for FullPriceMessage {
Ok(bytes)
}
}


#[repr(C)]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DummyPriceMessage {
pub header: MessageHeader,
pub data: Vec<u8>,
}


impl DummyPriceMessage {
pub const SIZE: usize = 1017;

pub fn new(msg_size: u16) -> Self {
Self {
header: MessageHeader::new(MessageSchema::Dummy, msg_size as u32),
data: vec![0u8; msg_size as usize],
}
}
}


impl AccumulatorSerializer for DummyPriceMessage {
fn accumulator_serialize(&self) -> Result<Vec<u8>> {
let mut bytes = vec![];
bytes.write_all(&self.header.schema.to_be_bytes())?;
bytes.write_all(&self.header.version.to_be_bytes())?;
bytes.write_all(&self.header.size.to_be_bytes())?;
bytes.extend_from_slice(&self.data);
Ok(bytes)
}
}
105 changes: 103 additions & 2 deletions accumulator_updater/tests/accumulator_updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const accumulatorUpdaterProgram = anchor.workspace
const mockCpiProg = anchor.workspace.MockCpiCaller as Program<MockCpiCaller>;
let whitelistAuthority = anchor.web3.Keypair.generate();
const [fundPda] = anchor.web3.PublicKey.findProgramAddressSync(
[Buffer.from("accumulator"), Buffer.from("fund")],
[Buffer.from("fund")],
accumulatorUpdaterProgram.programId
);

Expand Down Expand Up @@ -287,6 +287,107 @@ describe("accumulator_updater", () => {
assert.isTrue(pm.priceExpo.eq(updatePriceParams.priceExpo));
});
});

it("Mock CPI Program - CPI Max Test", async () => {
// with loosen CPI feature activated, max cpi instruction size len is 10KB
let testCases = [[1024], [1024, 2048], [1024, 2048, 4096]];
// for (let i = 1; i < 8; i++) {
for (let i = 0; i < testCases.length; i++) {
let testCase = testCases[i];
console.info(`testCase: ${testCase}`);
const updatePriceParams = {
price: new anchor.BN(10 * i + 5),
priceExpo: new anchor.BN(10 & (i + 6)),
ema: new anchor.BN(10 * i + 7),
emaExpo: new anchor.BN(10 * i + 8),
};

let accumulatorPdaMeta = getAccumulatorPdaMeta(pythPriceAccountPk);
await mockCpiProg.methods
.cpiMaxTest(updatePriceParams, testCase)
.accounts({
fund: fundPda,
pythPriceAccount: pythPriceAccountPk,
ixsSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
accumulatorWhitelist: whitelistPubkey,
accumulatorProgram: accumulatorUpdaterProgram.programId,
})
.remainingAccounts([accumulatorPdaMeta])
.preInstructions([
ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
])
.rpc({
skipPreflight: true,
});

const pythPriceAccount = await mockCpiProg.account.priceAccount.fetch(
pythPriceAccountPk
);
assert.isTrue(pythPriceAccount.price.eq(updatePriceParams.price));
assert.isTrue(pythPriceAccount.priceExpo.eq(updatePriceParams.priceExpo));
assert.isTrue(pythPriceAccount.ema.eq(updatePriceParams.ema));
assert.isTrue(pythPriceAccount.emaExpo.eq(updatePriceParams.emaExpo));
const accumulatorInput =
await accumulatorUpdaterProgram.account.accumulatorInput.fetch(
accumulatorPdaMeta.pubkey
);
const updatedAccumulatorPriceMessages =
parseAccumulatorInput(accumulatorInput);

console.log(
`updatedAccumulatorPriceMessages: ${JSON.stringify(
updatedAccumulatorPriceMessages,
null,
2
)}`
);
updatedAccumulatorPriceMessages.forEach((pm) => {
assert.isTrue(pm.id.eq(addPriceParams.id));
assert.isTrue(pm.price.eq(updatePriceParams.price));
assert.isTrue(pm.priceExpo.eq(updatePriceParams.priceExpo));
});
}
});

it("Mock CPI Program - CPI Max Test Fail", async () => {
// with loosen CPI feature activated, max cpi instruction size len is 10KB
let testCases = [[1024, 2048, 4096, 8192]];
// for (let i = 1; i < 8; i++) {
for (let i = 0; i < testCases.length; i++) {
let testCase = testCases[i];
console.info(`testCase: ${testCase}`);
const updatePriceParams = {
price: new anchor.BN(10 * i + 5),
priceExpo: new anchor.BN(10 & (i + 6)),
ema: new anchor.BN(10 * i + 7),
emaExpo: new anchor.BN(10 * i + 8),
};

let accumulatorPdaMeta = getAccumulatorPdaMeta(pythPriceAccountPk);
let errorThrown = false;
try {
await mockCpiProg.methods
.cpiMaxTest(updatePriceParams, testCase)
.accounts({
fund: fundPda,
pythPriceAccount: pythPriceAccountPk,
ixsSysvar: anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY,
accumulatorWhitelist: whitelistPubkey,
accumulatorProgram: accumulatorUpdaterProgram.programId,
})
.remainingAccounts([accumulatorPdaMeta])
.preInstructions([
ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
])
.rpc({
skipPreflight: true,
});
} catch (_err) {
errorThrown = true;
}
assert.ok(errorThrown);
}
});
});

export const getAccumulatorPdaMeta = (
Expand Down Expand Up @@ -339,7 +440,7 @@ function parseAccumulatorInput({
} else if (msgHeader.schema == 1) {
accumulatorMessages.push(parseCompactPriceMessage(msgData));
} else {
console.warn("Unknown input index: " + i);
console.warn("unknown msgHeader.schema: " + i);
continue;
}
start = endOffset;
Expand Down