Skip to content

Commit fca9f4a

Browse files
authored
Re-add TWAP messages, feature-gate price resizing, optimize caching behavior (#378)
* program/rust: Restore TWAP messages, feature-gate resize logic * Dockerfile, build-bpf.sh: Optimize caching behavior * .github: Add Solana for pre-commit runs * move testing-specific cargo-build-bpf call to pyth_simulator.rs * Update comments about features * review nits: Dockerfile - remove ls, utils.rs - ungate send_lamports
1 parent 270d24d commit fca9f4a

17 files changed

+272
-161
lines changed

.dockerignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
build
22
target
3+
program/rust/codegen

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docker/Dockerfile

+27-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
ARG SOLANA_VERSION
2-
ARG SOLANA_DOCKER_IMAGE_HASH
1+
ARG SOLANA_VERSION=v1.14.7
2+
ARG SOLANA_DOCKER_IMAGE_HASH=9130b4a26ec00c4ba99c023da98c06abb0dba2904c990af7770928e0f7dfd2d5
3+
34
FROM solanalabs/solana:v${SOLANA_VERSION}@sha256:${SOLANA_DOCKER_IMAGE_HASH}
45

56
# Redeclare SOLANA_VERSION in the new build stage.
67
# Persist in env for docker run & inspect.
7-
ARG SOLANA_VERSION
88
ENV SOLANA_VERSION="${SOLANA_VERSION}"
99

1010
RUN apt-get update
@@ -46,15 +46,35 @@ WORKDIR /home/pyth
4646
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
4747
| sh -s -- -y
4848

49-
COPY --chown=pyth:pyth . pyth-client/
50-
51-
# Build off-chain binaries.
52-
RUN cd pyth-client && ./scripts/build.sh
49+
ENV PATH=${PATH}:/home/pyth/.cargo/bin
5350

51+
COPY --chown=pyth:pyth ./scripts/get-bpf-tools.sh pyth-client/scripts/
5452
RUN ./pyth-client/scripts/get-bpf-tools.sh
53+
5554
# Apply patch to solana
55+
COPY --chown=pyth:pyth ./scripts/patch-solana.sh ./scripts/solana.patch pyth-client/scripts/
5656
RUN ./pyth-client/scripts/patch-solana.sh
5757

58+
# Copy C/C++ sources
59+
COPY --chown=pyth:pyth ./pc pyth-client/pc
60+
COPY --chown=pyth:pyth ./pctest pyth-client/pctest
61+
COPY --chown=pyth:pyth ./pcapps pyth-client/pcapps
62+
COPY --chown=pyth:pyth ./program/c pyth-client/program/c
63+
COPY --chown=pyth:pyth ./CMakeLists.txt pyth-client
64+
COPY --chown=pyth:pyth ./scripts/build.sh pyth-client/scripts/
65+
66+
# Build C/C++ offchain binaries.
67+
RUN cd pyth-client && ./scripts/build.sh
68+
69+
# layer-cache crates.io packages from Cargo.{toml|lock} and rustup toolchain
70+
COPY --chown=pyth:pyth program/rust/Cargo.toml pyth-client/program/rust/
71+
COPY --chown=pyth:pyth Cargo.toml Cargo.lock rust-toolchain pyth-client/
72+
RUN mkdir -p pyth-client/program/rust/src && touch pyth-client/program/rust/src/lib.rs
73+
RUN cd pyth-client && cargo fetch --locked
74+
75+
# Do final source code copy to overwrite the placeholder lib.rs
76+
COPY --chown=pyth:pyth ./ pyth-client/
77+
5878
# Build and test the oracle program.
5979
RUN cd pyth-client && ./scripts/build-bpf.sh .
6080

program/rust/.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
bindings.rs
1+
codegen/*

program/rust/Cargo.toml

+8
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,19 @@ pythnet-sdk = { git = "https://github.com/pyth-network/pyth-crosschain", rev="60
3333
serde_json = "1.0"
3434
test-generator = "0.3.1"
3535
csv = "1.1"
36+
lazy_static = "1.4.0"
3637

38+
# IMPORTANT: Features in this crate must be added to the `lazy_static` macro call in `pyth_simulator.rs`.
39+
#
40+
# Context: We perform a cargo-build-bpf call as part of running tests
41+
# to obtain a BPF binary for testing. Desired features are not known
42+
# to cargo-build-bpf at that point - we manually capture them at
43+
# compile-time and pass on to the child process.
3744
[features]
3845
debug = []
3946
library = []
4047
pythnet = []
48+
price_v2_resize = []
4149

4250
[lib]
4351
crate-type = ["cdylib", "lib"]

program/rust/build.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,6 @@ use {
88

99
fn main() {
1010
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
11-
// The tests depend on the BPF build (because we load the BPF program into the solana simulator).
12-
// Always build the bpf binary before doing anything else.
13-
if target_arch != "bpf" {
14-
Command::new("cargo").arg("build-bpf").status().unwrap();
15-
}
1611

1712
// OUT_DIR is the path cargo provides to a build directory under `target/` specifically for
1813
// isolated build artifacts. We use this to build the C program and then link against the
@@ -29,7 +24,6 @@ fn main() {
2924
}
3025
make_targets.push("test");
3126

32-
3327
// We must forward OUT_DIR as an env variable to the make script otherwise it will output
3428
// its artifacts to the wrong place.
3529
std::process::Command::new("make")
@@ -49,6 +43,13 @@ fn main() {
4943
println!("cargo:rustc-link-lib=static=cpyth-test");
5044
println!("cargo:rustc-link-search={}", out_dir.display());
5145

46+
std::fs::create_dir("./codegen").unwrap_or_else(|e| {
47+
eprintln!(
48+
"Could not create codegen directory (may exist which is fine), error: {}",
49+
e
50+
);
51+
});
52+
5253
// Generate and write bindings
5354
let bindings = Builder::default()
5455
.clang_arg(format!("-I{:}", get_solana_inc_path().display()))
@@ -57,7 +58,7 @@ fn main() {
5758
.generate()
5859
.expect("Unable to generate bindings");
5960
bindings
60-
.write_to_file("./bindings.rs")
61+
.write_to_file("./codegen/bindings.rs")
6162
.expect("Couldn't write bindings!");
6263

6364
// Rerun the build script if either the rust or C code changes

program/rust/src/accounts/price.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ pub struct PriceAccountV2 {
146146
pub price_cumulative: PriceCumulative,
147147
}
148148

149-
149+
#[allow(dead_code)]
150150
impl PriceAccountV2 {
151151
/// This function gets triggered when there's a succesful aggregation and updates the cumulative sums
152152
pub fn update_price_cumulative(&mut self) -> Result<(), OracleError> {
@@ -162,7 +162,6 @@ impl PriceAccountV2 {
162162
}
163163
}
164164

165-
#[allow(dead_code)]
166165
pub fn as_twap_message(&self, key: &Pubkey) -> TwapMessage {
167166
let publish_time = if self.agg_.status_ == PC_STATUS_TRADING {
168167
self.timestamp_
@@ -215,7 +214,6 @@ impl PriceCumulative {
215214
}
216215
}
217216

218-
219217
#[repr(C)]
220218
#[derive(Copy, Clone, Pod, Zeroable)]
221219
pub struct PriceComponent {

program/rust/src/c_oracle_header.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#![allow(dead_code)]
55

66
// Bindings.rs is generated by build.rs to include things defined in bindings.h
7-
include!("../bindings.rs");
7+
include!("../codegen/bindings.rs");
88

99
/// If ci > price / PC_MAX_CI_DIVISOR, set publisher status to unknown.
1010
/// (e.g., 20 means ci must be < 5% of price)

program/rust/src/processor.rs

+14-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ mod upd_permissions;
2727
mod upd_price;
2828
mod upd_product;
2929

30+
#[cfg(feature = "price_v2_resize")]
31+
pub use resize_price_account::resize_price_account;
3032
pub use {
3133
add_price::add_price,
3234
add_product::add_product,
@@ -36,7 +38,6 @@ pub use {
3638
del_publisher::del_publisher,
3739
init_mapping::init_mapping,
3840
init_price::init_price,
39-
resize_price_account::resize_price_account,
4041
set_min_pub::set_min_pub,
4142
upd_permissions::upd_permissions,
4243
upd_price::{
@@ -71,7 +72,18 @@ pub fn process_instruction(
7172
UpdTest => Err(OracleError::UnrecognizedInstruction.into()),
7273
SetMinPub => set_min_pub(program_id, accounts, instruction_data),
7374
UpdPriceNoFailOnError => upd_price_no_fail_on_error(program_id, accounts, instruction_data),
74-
ResizePriceAccount => resize_price_account(program_id, accounts, instruction_data),
75+
ResizePriceAccount => {
76+
#[cfg(feature = "price_v2_resize")]
77+
{
78+
resize_price_account(program_id, accounts, instruction_data)
79+
}
80+
81+
#[cfg(not(feature = "price_v2_resize"))]
82+
{
83+
solana_program::msg!("Oracle built with price_v2_resize disabled. Bailing out!");
84+
Err(OracleError::UnrecognizedInstruction.into())
85+
}
86+
}
7587
DelPrice => del_price(program_id, accounts, instruction_data),
7688
DelProduct => del_product(program_id, accounts, instruction_data),
7789
UpdPermissions => upd_permissions(program_id, accounts, instruction_data),

program/rust/src/processor/resize_price_account.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#![cfg(feature = "price_v2_resize")]
2+
13
use {
24
crate::{
35
accounts::{

program/rust/src/processor/upd_price.rs

+37-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#[cfg(feature = "pythnet")]
22
use {
33
crate::accounts::{
4+
PriceAccountV2,
5+
PythAccount,
46
PythOracleSerialize,
57
UPD_PRICE_WRITE_SEED,
68
},
@@ -14,9 +16,7 @@ use {
1416
crate::{
1517
accounts::{
1618
PriceAccount,
17-
PriceAccountV2,
1819
PriceInfo,
19-
PythAccount,
2020
},
2121
deserialize::{
2222
load,
@@ -160,7 +160,10 @@ pub fn upd_price(
160160
}
161161

162162
// Try to update the aggregate
163+
#[allow(unused_variables)]
163164
let mut aggregate_updated = false;
165+
166+
#[allow(unused_assignments)]
164167
if clock.slot > latest_aggregate_price.pub_slot_ {
165168
unsafe {
166169
aggregate_updated = c_upd_aggregate(
@@ -171,20 +174,39 @@ pub fn upd_price(
171174
}
172175
}
173176

177+
// TWAP message will be stored here if:
178+
// * price_account is V2
179+
// * accumulator accounts were passed
180+
#[cfg(feature = "pythnet")]
181+
let mut maybe_twap_msg = None;
174182

175-
let account_len = price_account.try_data_len()?;
176-
if account_len >= PriceAccountV2::MINIMUM_SIZE {
177-
let mut price_data =
183+
// Feature-gated PriceAccountV2-specific processing, used only on pythnet/pythtest. May populate
184+
// maybe_twap_msg for later use in accumulator message cross-call.
185+
//
186+
// NOTE: This is done here specifically in order to keep price
187+
// account data available for borrowing as v1 for remaining v1 processing.
188+
#[cfg(feature = "pythnet")]
189+
if price_account.try_data_len()? >= PriceAccountV2::MINIMUM_SIZE {
190+
let mut price_data_v2 =
178191
load_checked::<PriceAccountV2>(price_account, cmd_args.header.version)?;
179192

180193
if aggregate_updated {
181-
price_data.update_price_cumulative()?;
194+
// Update PriceCumulative on aggregation. This is done
195+
// here, because PriceCumulative must update
196+
// regardless of accumulator accounts being passed.
197+
price_data_v2.update_price_cumulative()?;
198+
}
199+
200+
// Populate the TWAP message if accumulator will be used
201+
if maybe_accumulator_accounts.is_some() {
202+
maybe_twap_msg = Some(price_data_v2.as_twap_message(price_account.key));
182203
}
183204
}
184205

206+
// Reload as V1 for remaining V1-compatible processing
185207
let mut price_data = load_checked::<PriceAccount>(price_account, cmd_args.header.version)?;
186208

187-
209+
// Feature-gated accumulator-specific code, used only on pythnet/pythtest
188210
#[cfg(feature = "pythnet")]
189211
{
190212
// We want to send a message every time the aggregate price updates. However, during the migration,
@@ -194,6 +216,7 @@ pub fn upd_price(
194216
if aggregate_updated {
195217
price_data.message_sent_ = 0;
196218
}
219+
197220
if let Some(accumulator_accounts) = maybe_accumulator_accounts {
198221
if price_data.message_sent_ == 0 {
199222
// Check that the oracle PDA is correctly configured for the program we are calling.
@@ -225,10 +248,16 @@ pub fn upd_price(
225248
is_writable: true,
226249
},
227250
];
228-
let message = vec![price_data
251+
252+
let mut message = vec![price_data
229253
.as_price_feed_message(price_account.key)
230254
.to_bytes()];
231255

256+
// Append a TWAP message if available
257+
if let Some(twap_msg) = maybe_twap_msg {
258+
message.push(twap_msg.to_bytes());
259+
}
260+
232261
// anchor discriminator for "global:put_all"
233262
let discriminator: [u8; 8] = [212, 225, 193, 91, 151, 238, 20, 93];
234263
let create_inputs_ix = Instruction::new_with_borsh(

0 commit comments

Comments
 (0)