Skip to content

Commit 5baf3c3

Browse files
jayantkJayant Krishnamurthy
and
Jayant Krishnamurthy
authored
Command to delete a product (#262)
* first commit of del_product * cleanup * PR comments Co-authored-by: Jayant Krishnamurthy <[email protected]>
1 parent 49fa49b commit 5baf3c3

File tree

7 files changed

+207
-0
lines changed

7 files changed

+207
-0
lines changed

program/c/src/oracle/oracle.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,12 @@ typedef enum {
275275
// key[1] product account [signer writable]
276276
// key[2] price account [signer writable]
277277
e_cmd_del_price,
278+
279+
// deletes a product account
280+
// key[0] funding account [signer writable]
281+
// key[1] mapping account [signer writable]
282+
// key[2] product account [signer writable]
283+
e_cmd_del_product,
278284
} command_t;
279285

280286
typedef struct cmd_hdr

program/rust/src/processor.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::c_oracle_header::{
66
command_t_e_cmd_add_publisher,
77
command_t_e_cmd_agg_price,
88
command_t_e_cmd_del_price,
9+
command_t_e_cmd_del_product,
910
command_t_e_cmd_del_publisher,
1011
command_t_e_cmd_init_mapping,
1112
command_t_e_cmd_init_price,
@@ -29,6 +30,7 @@ use crate::rust_oracle::{
2930
add_product,
3031
add_publisher,
3132
del_price,
33+
del_product,
3234
del_publisher,
3335
init_mapping,
3436
init_price,
@@ -75,6 +77,7 @@ pub fn process_instruction(
7577
command_t_e_cmd_upd_product => upd_product(program_id, accounts, instruction_data),
7678
command_t_e_cmd_set_min_pub => set_min_pub(program_id, accounts, instruction_data),
7779
command_t_e_cmd_del_price => del_price(program_id, accounts, instruction_data),
80+
command_t_e_cmd_del_product => del_product(program_id, accounts, instruction_data),
7881
_ => Err(OracleError::UnrecognizedInstruction.into()),
7982
}
8083
}

program/rust/src/rust_oracle.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ use crate::utils::{
6262
check_valid_writable_account,
6363
is_component_update,
6464
pubkey_assign,
65+
pubkey_clear,
6566
pubkey_equal,
6667
pubkey_is_zero,
6768
pyth_assert,
@@ -705,3 +706,72 @@ pub fn set_min_pub(
705706

706707
Ok(())
707708
}
709+
710+
/// Delete a product account and remove it from the product list of its associated mapping account.
711+
/// The deleted product account must not have any price accounts.
712+
///
713+
/// This function allows you to delete products from non-tail mapping accounts. This ability is a
714+
/// little weird, as it allows you to construct a list of multiple mapping accounts where non-tail
715+
/// accounts have empty space. This is fine however; users should simply add new products to the
716+
/// first available spot.
717+
pub fn del_product(
718+
program_id: &Pubkey,
719+
accounts: &[AccountInfo],
720+
instruction_data: &[u8],
721+
) -> ProgramResult {
722+
let [funding_account, mapping_account, product_account] = match accounts {
723+
[w, x, y] => Ok([w, x, y]),
724+
_ => Err(ProgramError::InvalidArgument),
725+
}?;
726+
727+
check_valid_funding_account(funding_account)?;
728+
check_valid_signable_account(program_id, mapping_account, size_of::<pc_map_table_t>())?;
729+
check_valid_signable_account(program_id, product_account, PC_PROD_ACC_SIZE as usize)?;
730+
731+
{
732+
let cmd_args = load::<cmd_hdr_t>(instruction_data)?;
733+
let mut mapping_data = load_checked::<pc_map_table_t>(mapping_account, cmd_args.ver_)?;
734+
let product_data = load_checked::<pc_prod_t>(product_account, cmd_args.ver_)?;
735+
736+
// This assertion is just to make the subtractions below simpler
737+
pyth_assert(mapping_data.num_ >= 1, ProgramError::InvalidArgument)?;
738+
pyth_assert(
739+
pubkey_is_zero(&product_data.px_acc_),
740+
ProgramError::InvalidArgument,
741+
)?;
742+
743+
let product_key = product_account.key.to_bytes();
744+
let product_index = mapping_data
745+
.prod_
746+
.iter()
747+
.position(|x| pubkey_equal(x, &product_key))
748+
.ok_or(ProgramError::InvalidArgument)?;
749+
750+
let num_after_removal: usize = try_convert(
751+
mapping_data
752+
.num_
753+
.checked_sub(1)
754+
.ok_or(ProgramError::InvalidArgument)?,
755+
)?;
756+
757+
let last_key_bytes = mapping_data.prod_[num_after_removal];
758+
pubkey_assign(
759+
&mut mapping_data.prod_[product_index],
760+
bytes_of(&last_key_bytes),
761+
);
762+
pubkey_clear(&mut mapping_data.prod_[num_after_removal]);
763+
mapping_data.num_ = try_convert::<_, u32>(num_after_removal)?;
764+
mapping_data.size_ =
765+
try_convert::<_, u32>(size_of::<pc_map_table_t>() - size_of_val(&mapping_data.prod_))?
766+
+ mapping_data.num_ * try_convert::<_, u32>(size_of::<pc_pub_key_t>())?;
767+
}
768+
769+
// Zero out the balance of the price account to delete it.
770+
// Note that you can't use the system program's transfer instruction to do this operation, as
771+
// that instruction fails if the source account has any data.
772+
let lamports = product_account.lamports();
773+
**product_account.lamports.borrow_mut() = 0;
774+
**funding_account.lamports.borrow_mut() += lamports;
775+
776+
Ok(())
777+
}

program/rust/src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod test_add_price;
44
mod test_add_product;
55
mod test_add_publisher;
66
mod test_del_price;
7+
mod test_del_product;
78
mod test_del_publisher;
89
mod test_init_mapping;
910
mod test_init_price;

program/rust/src/tests/pyth_simulator.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use crate::c_oracle_header::{
3232
command_t_e_cmd_add_price,
3333
command_t_e_cmd_add_product,
3434
command_t_e_cmd_del_price,
35+
command_t_e_cmd_del_product,
3536
command_t_e_cmd_init_mapping,
3637
pc_map_table_t,
3738
pc_price_t,
@@ -161,6 +162,30 @@ impl PythSimulator {
161162
.map(|_| product_keypair)
162163
}
163164

165+
/// Delete a product account (using the del_product instruction).
166+
pub async fn del_product(
167+
&mut self,
168+
mapping_keypair: &Keypair,
169+
product_keypair: &Keypair,
170+
) -> Result<(), BanksClientError> {
171+
let cmd = cmd_hdr_t {
172+
ver_: PC_VERSION,
173+
cmd_: command_t_e_cmd_del_product as i32,
174+
};
175+
let instruction = Instruction::new_with_bytes(
176+
self.program_id,
177+
bytes_of(&cmd),
178+
vec![
179+
AccountMeta::new(self.payer.pubkey(), true),
180+
AccountMeta::new(mapping_keypair.pubkey(), true),
181+
AccountMeta::new(product_keypair.pubkey(), true),
182+
],
183+
);
184+
185+
self.process_ix(instruction, &vec![&mapping_keypair, &product_keypair])
186+
.await
187+
}
188+
164189
/// Initialize a price account and add it to an existing product account (using the add_price
165190
/// instruction). Returns the keypair associated with the newly-created account.
166191
pub async fn add_price(
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
use std::mem::{
2+
size_of,
3+
size_of_val,
4+
};
5+
6+
use solana_program::pubkey::Pubkey;
7+
use solana_sdk::signer::Signer;
8+
9+
use crate::c_oracle_header::{
10+
pc_map_table_t,
11+
pc_pub_key_t,
12+
};
13+
use crate::tests::pyth_simulator::PythSimulator;
14+
use crate::utils::pubkey_equal;
15+
16+
#[tokio::test]
17+
async fn test_del_product() {
18+
let mut sim = PythSimulator::new().await;
19+
let mapping_keypair = sim.init_mapping().await.unwrap();
20+
let product1 = sim.add_product(&mapping_keypair).await.unwrap();
21+
let product2 = sim.add_product(&mapping_keypair).await.unwrap();
22+
let product3 = sim.add_product(&mapping_keypair).await.unwrap();
23+
let product4 = sim.add_product(&mapping_keypair).await.unwrap();
24+
let product5 = sim.add_product(&mapping_keypair).await.unwrap();
25+
let _price3 = sim.add_price(&product3, -8).await.unwrap();
26+
27+
let mapping_keypair2 = sim.init_mapping().await.unwrap();
28+
let product1_2 = sim.add_product(&mapping_keypair2).await.unwrap();
29+
30+
assert!(sim.get_account(product2.pubkey()).await.is_some());
31+
assert!(sim.get_account(product4.pubkey()).await.is_some());
32+
33+
// Can't delete a product with a price account
34+
assert!(sim.del_product(&mapping_keypair, &product3).await.is_err());
35+
// Can't delete mismatched product/mapping accounts
36+
assert!(sim
37+
.del_product(&mapping_keypair, &product1_2)
38+
.await
39+
.is_err());
40+
41+
assert!(sim.del_product(&mapping_keypair, &product2).await.is_ok());
42+
43+
assert!(sim.get_account(product2.pubkey()).await.is_none());
44+
45+
let mapping_data = sim
46+
.get_account_data_as::<pc_map_table_t>(mapping_keypair.pubkey())
47+
.await
48+
.unwrap();
49+
assert!(mapping_product_list_equals(
50+
&mapping_data,
51+
vec![
52+
product1.pubkey(),
53+
product5.pubkey(),
54+
product3.pubkey(),
55+
product4.pubkey()
56+
]
57+
));
58+
assert!(sim.get_account(product5.pubkey()).await.is_some());
59+
60+
61+
assert!(sim.del_product(&mapping_keypair, &product4).await.is_ok());
62+
let mapping_data = sim
63+
.get_account_data_as::<pc_map_table_t>(mapping_keypair.pubkey())
64+
.await
65+
.unwrap();
66+
67+
assert!(mapping_product_list_equals(
68+
&mapping_data,
69+
vec![product1.pubkey(), product5.pubkey(), product3.pubkey()]
70+
));
71+
}
72+
73+
/// Returns true if the list of products in `mapping_data` contains the keys in `expected` (in the
74+
/// same order). Also checks `mapping_data.num_` and `size_`.
75+
fn mapping_product_list_equals(mapping_data: &pc_map_table_t, expected: Vec<Pubkey>) -> bool {
76+
if mapping_data.num_ != expected.len() as u32 {
77+
return false;
78+
}
79+
80+
let expected_size = (size_of::<pc_map_table_t>() - size_of_val(&mapping_data.prod_)
81+
+ expected.len() * size_of::<pc_pub_key_t>()) as u32;
82+
if mapping_data.size_ != expected_size {
83+
return false;
84+
}
85+
86+
for i in 0..expected.len() {
87+
if !pubkey_equal(&mapping_data.prod_[i], &expected[i].to_bytes()) {
88+
return false;
89+
}
90+
}
91+
92+
return true;
93+
}

program/rust/src/utils.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,15 @@ pub fn pubkey_equal(target: &pc_pub_key_t, source: &[u8]) -> bool {
106106
unsafe { target.k1_ == *source }
107107
}
108108

109+
/// Zero out the bytes of `key`.
110+
pub fn pubkey_clear(key: &mut pc_pub_key_t) {
111+
unsafe {
112+
for i in 0..key.k8_.len() {
113+
key.k8_[i] = 0;
114+
}
115+
}
116+
}
117+
109118
/// Convert `x: T` into a `U`, returning the appropriate `OracleError` if the conversion fails.
110119
pub fn try_convert<T, U: TryFrom<T>>(x: T) -> Result<U, OracleError> {
111120
// Note: the error here assumes we're only applying this function to integers right now.

0 commit comments

Comments
 (0)