Skip to content

Commit ca0c981

Browse files
committed
Revert "feat: aggregate in the same slot (#394)"
This reverts commit 65949e0.
1 parent 65949e0 commit ca0c981

16 files changed

+667
-701
lines changed

.gitignore

+2-5
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,9 @@ target
55
cmake-build-*
66
/venv
77

8-
# CMake files
9-
features.h
10-
118
# IntelliJ / CLion configuration
129
.idea
1310
*.iml
1411

15-
# Clang format
16-
.clang-format
12+
# CMake files
13+
features.h

program/c/src/oracle/oracle.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ extern "C" {
2323
#define PC_MAP_TABLE_SIZE 640
2424

2525
// Total price component slots available
26-
#define PC_NUM_COMP_PYTHNET 127
26+
#define PC_NUM_COMP_PYTHNET 128
2727

2828
// PC_NUM_COMP - number of price components in use
2929
// Not whole PC_NUM_COMP_PYTHNET because of stack issues appearing in upd_aggregate()

program/c/src/oracle/upd_aggregate.h

+8
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,14 @@ static inline void upd_twap(
134134
// update aggregate price
135135
static inline bool upd_aggregate( pc_price_t *ptr, uint64_t slot, int64_t timestamp )
136136
{
137+
// Update the value of the previous price, if it had TRADING status.
138+
if ( ptr->agg_.status_ == PC_STATUS_TRADING ) {
139+
ptr->prev_slot_ = ptr->agg_.pub_slot_;
140+
ptr->prev_price_ = ptr->agg_.price_;
141+
ptr->prev_conf_ = ptr->agg_.conf_;
142+
ptr->prev_timestamp_ = ptr->timestamp_;
143+
}
144+
137145
// update aggregate details ready for next slot
138146
ptr->valid_slot_ = ptr->agg_.pub_slot_;// valid slot-time of agg. price
139147
ptr->agg_.pub_slot_ = slot; // publish slot-time of agg. price

program/rust/src/accounts/price.rs

+24-75
Original file line numberDiff line numberDiff line change
@@ -31,113 +31,62 @@ mod price_pythnet {
3131
},
3232
error::OracleError,
3333
},
34-
std::ops::{
35-
Deref,
36-
DerefMut,
37-
Index,
38-
IndexMut,
39-
},
4034
};
4135

42-
#[repr(C)]
43-
#[derive(Copy, Clone)]
44-
pub struct PriceComponentArrayWrapper([PriceComponent; PC_NUM_COMP_PYTHNET as usize]);
45-
46-
// Implementing Index and IndexMut allows PriceComponentArrayWrapper to use array indexing directly,
47-
// such as price_account.comp_[i], making it behave more like a native array or slice.
48-
impl Index<usize> for PriceComponentArrayWrapper {
49-
type Output = PriceComponent;
50-
51-
fn index(&self, index: usize) -> &Self::Output {
52-
&self.0[index]
53-
}
54-
}
55-
impl IndexMut<usize> for PriceComponentArrayWrapper {
56-
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
57-
&mut self.0[index]
58-
}
59-
}
60-
61-
// Implementing Deref and DerefMut allows PriceComponentArrayWrapper to use slice methods directly,
62-
// such as len(), making it behave more like a native array or slice.
63-
impl Deref for PriceComponentArrayWrapper {
64-
type Target = [PriceComponent];
65-
66-
fn deref(&self) -> &Self::Target {
67-
&self.0
68-
}
69-
}
70-
71-
impl DerefMut for PriceComponentArrayWrapper {
72-
fn deref_mut(&mut self) -> &mut Self::Target {
73-
&mut self.0
74-
}
75-
}
76-
77-
unsafe impl Pod for PriceComponentArrayWrapper {
78-
}
79-
unsafe impl Zeroable for PriceComponentArrayWrapper {
80-
}
81-
8236
/// Pythnet-only extended price account format. This extension is
8337
/// an append-only change that adds extra publisher slots and
8438
/// PriceCumulative for TWAP processing.
8539
#[repr(C)]
8640
#[derive(Copy, Clone, Pod, Zeroable)]
8741
pub struct PriceAccountPythnet {
88-
pub header: AccountHeader,
42+
pub header: AccountHeader,
8943
/// Type of the price account
90-
pub price_type: u32,
44+
pub price_type: u32,
9145
/// Exponent for the published prices
92-
pub exponent: i32,
46+
pub exponent: i32,
9347
/// Current number of authorized publishers
94-
pub num_: u32,
48+
pub num_: u32,
9549
/// Number of valid quotes for the last aggregation
96-
pub num_qt_: u32,
50+
pub num_qt_: u32,
9751
/// Last slot with a succesful aggregation (status : TRADING)
98-
pub last_slot_: u64,
52+
pub last_slot_: u64,
9953
/// Second to last slot where aggregation was attempted
100-
pub valid_slot_: u64,
54+
pub valid_slot_: u64,
10155
/// Ema for price
102-
pub twap_: PriceEma,
56+
pub twap_: PriceEma,
10357
/// Ema for confidence
104-
pub twac_: PriceEma,
58+
pub twac_: PriceEma,
10559
/// Last time aggregation was attempted
106-
pub timestamp_: i64,
60+
pub timestamp_: i64,
10761
/// Minimum valid publisher quotes for a succesful aggregation
108-
pub min_pub_: u8,
109-
pub message_sent_: u8,
62+
pub min_pub_: u8,
63+
pub message_sent_: u8,
11064
/// Configurable max latency in slots between send and receive
111-
pub max_latency_: u8,
65+
pub max_latency_: u8,
11266
/// Unused placeholder for alignment
113-
pub unused_2_: i8,
114-
pub unused_3_: i32,
67+
pub unused_2_: i8,
68+
pub unused_3_: i32,
11569
/// Corresponding product account
116-
pub product_account: Pubkey,
70+
pub product_account: Pubkey,
11771
/// Next price account in the list
118-
pub next_price_account: Pubkey,
72+
pub next_price_account: Pubkey,
11973
/// Second to last slot where aggregation was succesful (i.e. status : TRADING)
120-
pub prev_slot_: u64,
74+
pub prev_slot_: u64,
12175
/// Aggregate price at prev_slot_
122-
pub prev_price_: i64,
76+
pub prev_price_: i64,
12377
/// Confidence interval at prev_slot_
124-
pub prev_conf_: u64,
78+
pub prev_conf_: u64,
12579
/// Timestamp of prev_slot_
126-
pub prev_timestamp_: i64,
80+
pub prev_timestamp_: i64,
12781
/// Last attempted aggregate results
128-
pub agg_: PriceInfo,
82+
pub agg_: PriceInfo,
12983
/// Publishers' price components. NOTE(2023-10-06): On Pythnet, not all
13084
/// PC_NUM_COMP_PYTHNET slots are used due to stack size
13185
/// issues in the C code. For iterating over price components,
13286
/// PC_NUM_COMP must be used.
133-
pub comp_: PriceComponentArrayWrapper,
134-
/// Previous EMA for price and confidence
135-
pub prev_twap_: PriceEma,
136-
pub prev_twac_: PriceEma,
137-
/// Previous TWAP cumulative values
138-
pub prev_price_cumulative: PriceCumulative,
87+
pub comp_: [PriceComponent; PC_NUM_COMP_PYTHNET as usize],
13988
/// Cumulative sums of aggregative price and confidence used to compute arithmetic moving averages
140-
pub price_cumulative: PriceCumulative,
89+
pub price_cumulative: PriceCumulative,
14190
}
14291

14392
impl PriceAccountPythnet {

program/rust/src/processor/upd_price.rs

+36-73
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use {
22
crate::{
33
accounts::{
44
PriceAccount,
5+
PriceInfo,
56
PythOracleSerialize,
67
UPD_PRICE_WRITE_SEED,
78
},
8-
c_oracle_header::PC_STATUS_TRADING,
99
deserialize::{
1010
load,
1111
load_checked,
@@ -128,8 +128,7 @@ pub fn upd_price(
128128
let clock = Clock::from_account_info(clock_account)?;
129129

130130
let mut publisher_index: usize = 0;
131-
let slots_since_last_successful_aggregate: u64;
132-
let noninitial_price_update_after_program_upgrade: bool;
131+
let latest_aggregate_price: PriceInfo;
133132

134133
// The price_data borrow happens in a scope because it must be
135134
// dropped before we borrow again as raw data pointer for the C
@@ -150,16 +149,7 @@ pub fn upd_price(
150149
OracleError::PermissionViolation.into(),
151150
)?;
152151

153-
// We use last_slot_ to calculate slots_since_last_successful_aggregate. This is because last_slot_ is updated after the aggregate price is updated successfully.
154-
slots_since_last_successful_aggregate = clock.slot - price_data.last_slot_;
155-
156-
// Check if the program upgrade has happened in the current slot and aggregate price has been updated, if so, use the old logic to update twap/twac/price_cumulative.
157-
// This is to ensure that twap/twac/price_cumulative are calculated correctly during the migration.
158-
// We check if prev_twap_.denom_ is == 0 because when the program upgrade has happened, denom_ is initialized to 0 and it can only stay the same or increase while numer_ can be negative if prices are negative.
159-
// And we check if slots_since_last_successful_aggregate == 0 to check if the aggregate price has been updated in the current slot.
160-
noninitial_price_update_after_program_upgrade =
161-
price_data.prev_twap_.denom_ == 0 && slots_since_last_successful_aggregate == 0;
162-
152+
latest_aggregate_price = price_data.agg_;
163153
let latest_publisher_price = price_data.comp_[publisher_index].latest_;
164154

165155
// Check that publisher is publishing a more recent price
@@ -171,38 +161,10 @@ pub fn upd_price(
171161
)?;
172162
}
173163

174-
// Extend the scope of the mutable borrow of price_data
175-
{
176-
let mut price_data = load_checked::<PriceAccount>(price_account, cmd_args.header.version)?;
177-
178-
// Update the publisher's price
179-
if is_component_update(cmd_args)? {
180-
let status: u32 = get_status_for_conf_price_ratio(
181-
cmd_args.price,
182-
cmd_args.confidence,
183-
cmd_args.status,
184-
)?;
185-
let publisher_price = &mut price_data.comp_[publisher_index].latest_;
186-
publisher_price.price_ = cmd_args.price;
187-
publisher_price.conf_ = cmd_args.confidence;
188-
publisher_price.status_ = status;
189-
publisher_price.pub_slot_ = cmd_args.publishing_slot;
190-
}
191-
192-
// If the price update is the first in the slot and the aggregate is trading, update the previous slot, price, conf, and timestamp.
193-
if slots_since_last_successful_aggregate > 0 && price_data.agg_.status_ == PC_STATUS_TRADING
194-
{
195-
price_data.prev_slot_ = price_data.agg_.pub_slot_;
196-
price_data.prev_price_ = price_data.agg_.price_;
197-
price_data.prev_conf_ = price_data.agg_.conf_;
198-
price_data.prev_timestamp_ = price_data.timestamp_;
199-
}
200-
}
201-
202-
let updated = unsafe {
203-
if noninitial_price_update_after_program_upgrade {
204-
false
205-
} else {
164+
// Try to update the aggregate
165+
#[allow(unused_variables)]
166+
if clock.slot > latest_aggregate_price.pub_slot_ {
167+
let updated = unsafe {
206168
// NOTE: c_upd_aggregate must use a raw pointer to price
207169
// data. Solana's `<account>.borrow_*` methods require exclusive
208170
// access, i.e. no other borrow can exist for the account.
@@ -211,41 +173,25 @@ pub fn upd_price(
211173
clock.slot,
212174
clock.unix_timestamp,
213175
)
214-
}
215-
};
216-
217-
// If the aggregate was successfully updated, calculate the difference and update TWAP.
218-
if updated {
219-
{
176+
};
177+
178+
// If the aggregate was successfully updated, calculate the difference and update TWAP.
179+
if updated {
180+
let agg_diff = (clock.slot as i64)
181+
- load_checked::<PriceAccount>(price_account, cmd_args.header.version)?.prev_slot_
182+
as i64;
183+
// Encapsulate TWAP update logic in a function to minimize unsafe block scope.
184+
unsafe {
185+
c_upd_twap(price_account.try_borrow_mut_data()?.as_mut_ptr(), agg_diff);
186+
}
220187
let mut price_data =
221188
load_checked::<PriceAccount>(price_account, cmd_args.header.version)?;
222-
223-
// Multiple price updates may occur within the same slot. Updates within the same slot will
224-
// use the previously calculated values (prev_twap, prev_twac, and prev_price_cumulative)
225-
// from the last successful aggregated price update as their basis for recalculation. This
226-
// ensures that each update within a slot builds upon the last and not the twap/twac/price_cumulative
227-
// that is calculated right after the publishers' individual price updates.
228-
if slots_since_last_successful_aggregate > 0 {
229-
price_data.prev_twap_ = price_data.twap_;
230-
price_data.prev_twac_ = price_data.twac_;
231-
price_data.prev_price_cumulative = price_data.price_cumulative;
232-
}
233-
price_data.twap_ = price_data.prev_twap_;
234-
price_data.twac_ = price_data.prev_twac_;
235-
price_data.price_cumulative = price_data.prev_price_cumulative;
236-
237-
price_data.update_price_cumulative()?;
238189
// We want to send a message every time the aggregate price updates. However, during the migration,
239190
// not every publisher will necessarily provide the accumulator accounts. The message_sent_ flag
240191
// ensures that after every aggregate update, the next publisher who provides the accumulator accounts
241192
// will send the message.
242193
price_data.message_sent_ = 0;
243-
}
244-
let agg_diff = (clock.slot as i64)
245-
- load_checked::<PriceAccount>(price_account, cmd_args.header.version)?.prev_slot_
246-
as i64;
247-
unsafe {
248-
c_upd_twap(price_account.try_borrow_mut_data()?.as_mut_ptr(), agg_diff);
194+
price_data.update_price_cumulative()?;
249195
}
250196
}
251197

@@ -315,6 +261,23 @@ pub fn upd_price(
315261
}
316262
}
317263

264+
// Try to update the publisher's price
265+
if is_component_update(cmd_args)? {
266+
// IMPORTANT: If the publisher does not meet the price/conf
267+
// ratio condition, its price will not count for the next
268+
// aggregate.
269+
let status: u32 =
270+
get_status_for_conf_price_ratio(cmd_args.price, cmd_args.confidence, cmd_args.status)?;
271+
272+
{
273+
let publisher_price = &mut price_data.comp_[publisher_index].latest_;
274+
publisher_price.price_ = cmd_args.price;
275+
publisher_price.conf_ = cmd_args.confidence;
276+
publisher_price.status_ = status;
277+
publisher_price.pub_slot_ = cmd_args.publishing_slot;
278+
}
279+
}
280+
318281
Ok(())
319282
}
320283

program/rust/src/tests/mod.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ mod test_publish_batch;
1919
mod test_set_max_latency;
2020
mod test_set_min_pub;
2121
mod test_sizes;
22-
mod test_twap;
2322
mod test_upd_aggregate;
2423
mod test_upd_permissions;
2524
mod test_upd_price;
2625
mod test_upd_price_no_fail_on_error;
2726
mod test_upd_product;
2827
mod test_utils;
28+
29+
30+
mod test_twap;
31+
mod test_upd_price_v2;

0 commit comments

Comments
 (0)