Skip to content

Commit 793accf

Browse files
authored
Merge branch 'main' into same-slot-agg
2 parents a57bce5 + 46caea4 commit 793accf

28 files changed

+535
-164
lines changed

.gitignore

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,7 @@ features.h
1010

1111
# IntelliJ / CLion configuration
1212
.idea
13-
*.iml
13+
*.iml
14+
15+
# CMake files
16+
features.h

Cargo.lock

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

README.md

+20-4
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@ First, make sure you have the [solana tool suite](https://docs.solana.com/cli/in
1313
installed on your machine. (The build depends on some C makefiles that are in the tool suite.)
1414
Make sure you have installed the same solana version that is being used in [CI](.github/workflows/docker.yaml)
1515

16-
Then, simply run `cargo build` to compile the oracle program as a native binary, or `cargo build-bpf` to build a BPF binary
16+
Then, simply run `cargo build` to compile the oracle program as a native binary, or `cargo build-bpf` to build a BPF binary
1717
that can be uploaded to the blockchain. This step will produce a program binary `target/deploy/pyth_oracle.so`.
1818

1919
### Testing
2020

21-
Simply run `cargo test`. This command will run several sets of tests:
21+
Simply run `cargo test`. This command will run several sets of tests:
2222

23-
- Unit tests of individual functions
23+
- Unit tests of individual functions
2424
- Simulated transaction tests against the BPF binary running on a solana simulator
2525
- Exhaustive / randomized test batteries for core oracle functionality
2626

@@ -30,6 +30,7 @@ The C tests are linked into the rust binary so they run as part of `cargo test`
3030
You can also run `cargo test-bpf`, which runs the same tests as `cargo test`, though it's slightly slower and the UX is worse.
3131

3232
### pre-commit hooks
33+
3334
pre-commit is a tool that checks and fixes simple issues (formatting, ...) before each commit. You can install it by following [their website](https://pre-commit.com/). In order to enable checks for this repo run `pre-commit install` from command-line in the root of this repo.
3435

3536
The checks are also performed in the CI to ensure the code follows consistent formatting. Formatting is only currently enforced in the `program/` directory.
@@ -38,7 +39,7 @@ You might also need to install the nightly toolchain to run the formatting by ru
3839
## pythd (off-chain client API)
3940

4041
> :warning: pythd is deprecated and has been replaced by [pyth-agent](https://github.com/pyth-network/pyth-agent).
41-
> This new client is backward compatible with pythd, but more stable and configurable.
42+
> This new client is backward compatible with pythd, but more stable and configurable.
4243
4344
`pythd` provides exposes a web API for interacting with the on-chain oracle program.
4445

@@ -139,6 +140,7 @@ docker run --name pyth-dev -d \\
139140
```
140141

141142
Default user in the image is `pyth` which may not have access to your directories. Assign your user id and group id to it to enable access.
143+
142144
```
143145
host@host$ id $USER # Shows user_id, group_id, and group names
144146
host@host$ docker exec -ti pyth-dev bash
@@ -148,3 +150,17 @@ root@pyth-dev# usermod -u 1002 -g 1004 -s /bin/bash pyth
148150
```
149151

150152
Finally, in docker extension inside VS Code click right and choose "Attach VS Code". If you're using a remote host in VS Code make sure to let this connection be open.
153+
154+
## Deployment
155+
156+
Oracle program upgrades are managed by the Pythian Council multisig. The steps to deploy a new version are:
157+
158+
1. Create a release branch from `main`. This should include binaries for both the Solana mainnet and Pythnet oracle programs (`pyth_oracle_solana.so` and `pyth_oracle_pythnet.so`).
159+
2. [Install Solana CLI](https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-tool) if not already installed.
160+
3. Set Solana config for the target network, e.g., devnet: `solana config set --url https://api.devnet.solana.com`.
161+
4. Execute `solana program write-buffer <ORACLE_PROGRAM_BINARY_FILENAME>` where `<ORACLE_PROGRAM_BINARY_FILENAME>` can be `pyth_oracle_solana.so` or `pyth_oracle_pythnet.so` to obtain the buffer address.
162+
5. Run `solana program show <ORACLE_PROGRAM_PUBKEY>` to obtain the authority of the current program.
163+
6. Use `solana program set-buffer-authority <BUFFER_PUBKEY> --new-buffer-authority <NEW_BUFFER_AUTHORITY>` to assign the upgrade authority from the previous step to the buffer address.
164+
7. Submit a proposal with [`xc-admin`](https://github.com/pyth-network/pyth-crosschain/tree/main/governance/xc_admin/packages/xc_admin_cli) for program upgrade using the `upgrade-program` command.
165+
8. Verify the buffer by running `solana program dump <BUFFER> temp_file && shasum -a 256 temp_file && rm temp_file`, comparing the hash with the one from [build-bpf.sh#L35](https://github.com/pyth-network/pyth-client/blob/main/scripts/build-bpf.sh#L35).
166+
9. Once the proposal secures enough signatures, it will be automatically relayed to the target network, upgrading the program.

program/c/src/oracle/oracle.h

+3-2
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,9 @@ typedef struct pc_price
195195
pc_ema_t twac_; // time-weighted average conf interval
196196
int64_t timestamp_; // unix timestamp of aggregate price
197197
uint8_t min_pub_; // min publishers for valid price
198-
int8_t drv2_; // space for future derived values
199-
int16_t drv3_; // space for future derived values
198+
int8_t message_sent_; // flag to indicate if the current aggregate price has been sent as a message to the message buffer, 0 if not sent, 1 if sent
199+
uint8_t max_latency_; // configurable max latency in slots between send and receive
200+
int8_t drv3_; // space for future derived values
200201
int32_t drv4_; // space for future derived values
201202
pc_pub_key_t prod_; // product id/ref-account
202203
pc_pub_key_t next_; // next price account in list

program/c/src/oracle/upd_aggregate.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -173,10 +173,13 @@ static inline bool upd_aggregate( pc_price_t *ptr, uint64_t slot, int64_t timest
173173
int64_t slot_diff = ( int64_t )slot - ( int64_t )( iptr->agg_.pub_slot_ );
174174
int64_t price = iptr->agg_.price_;
175175
int64_t conf = ( int64_t )( iptr->agg_.conf_ );
176+
int64_t max_latency = ptr->max_latency_ ? ptr->max_latency_ : PC_MAX_SEND_LATENCY;
176177
if ( iptr->agg_.status_ == PC_STATUS_TRADING &&
177178
// No overflow for INT64_MIN+conf or INT64_MAX-conf as 0 < conf < INT64_MAX
178179
// These checks ensure that price - conf and price + conf do not overflow.
179-
(int64_t)0 < conf && (INT64_MIN + conf) <= price && price <= (INT64_MAX-conf) && slot_diff <= PC_MAX_SEND_LATENCY ) {
180+
(int64_t)0 < conf && (INT64_MIN + conf) <= price && price <= (INT64_MAX-conf) &&
181+
// slot_diff is implicitly >= 0 due to the check in Rust code ensuring publishing_slot is always less than or equal to the current slot.
182+
slot_diff <= max_latency ) {
180183
numv += 1;
181184
prcs[ nprcs++ ] = price - conf;
182185
prcs[ nprcs++ ] = price;

program/rust/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pyth-oracle"
3-
version = "2.25.0"
3+
version = "2.26.0"
44
edition = "2021"
55
license = "Apache 2.0"
66
publish = false

program/rust/src/accounts/price.rs

+17-4
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,10 @@ mod price_pythnet {
6565
/// Minimum valid publisher quotes for a succesful aggregation
6666
pub min_pub_: u8,
6767
pub message_sent_: u8,
68-
pub unused_2_: i16,
68+
/// Configurable max latency in slots between send and receive
69+
pub max_latency_: u8,
70+
/// Unused placeholder for alignment
71+
pub unused_2_: i8,
6972
pub unused_3_: i32,
7073
/// Corresponding product account
7174
pub product_account: Pubkey,
@@ -116,6 +119,7 @@ mod price_pythnet {
116119
self.agg_.price_,
117120
self.agg_.conf_,
118121
self.agg_.pub_slot_.saturating_sub(self.prev_slot_),
122+
self.max_latency_,
119123
); // pub_slot should always be >= prev_slot, but we protect ourselves against underflow just in case
120124
Ok(())
121125
} else {
@@ -172,11 +176,17 @@ mod price_pythnet {
172176
}
173177

174178
impl PriceCumulative {
175-
pub fn update(&mut self, price: i64, conf: u64, slot_gap: u64) {
179+
pub fn update(&mut self, price: i64, conf: u64, slot_gap: u64, max_latency: u8) {
176180
self.price += i128::from(price) * i128::from(slot_gap);
177181
self.conf += u128::from(conf) * u128::from(slot_gap);
182+
// Use PC_MAX_SEND_LATENCY if max_latency is 0, otherwise use max_latency
183+
let latency = if max_latency == 0 {
184+
u64::from(PC_MAX_SEND_LATENCY)
185+
} else {
186+
u64::from(max_latency)
187+
};
178188
// This is expected to saturate at 0 most of the time (while the feed is up).
179-
self.num_down_slots += slot_gap.saturating_sub(PC_MAX_SEND_LATENCY.into());
189+
self.num_down_slots += slot_gap.saturating_sub(latency);
180190
}
181191
}
182192
}
@@ -225,7 +235,10 @@ mod price_solana {
225235
/// Whether the current aggregate price has been sent as a message to the message buffer.
226236
/// 0 = false, 1 = true. (this is a u8 to make the Pod trait happy)
227237
pub message_sent_: u8,
228-
pub unused_2_: i16,
238+
/// Configurable max latency in slots between send and receive
239+
pub max_latency_: u8,
240+
/// Unused placeholder for alignment
241+
pub unused_2_: i8,
229242
pub unused_3_: i32,
230243
/// Corresponding product account
231244
pub product_account: Pubkey,

program/rust/src/instruction.rs

+12
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ pub enum OracleCommand {
9999
// key[2] permissions account [writable]
100100
// key[3] system program []
101101
UpdPermissions = 17,
102+
/// Set max latency
103+
// account[0] funding account [signer writable]
104+
// account[1] price account [signer writable]
105+
SetMaxLatency = 18,
102106
}
103107

104108
#[repr(C)]
@@ -162,3 +166,11 @@ pub struct UpdPermissionsArgs {
162166
pub data_curation_authority: Pubkey,
163167
pub security_authority: Pubkey,
164168
}
169+
170+
#[repr(C)]
171+
#[derive(Zeroable, Clone, Copy, Pod)]
172+
pub struct SetMaxLatencyArgs {
173+
pub header: CommandHeader,
174+
pub max_latency: u8,
175+
pub unused_: [u8; 3],
176+
}

program/rust/src/processor.rs

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ mod del_product;
2121
mod del_publisher;
2222
mod init_mapping;
2323
mod init_price;
24+
mod set_max_latency;
2425
mod set_min_pub;
2526
mod upd_permissions;
2627
mod upd_price;
@@ -35,6 +36,7 @@ pub use {
3536
del_publisher::del_publisher,
3637
init_mapping::init_mapping,
3738
init_price::init_price,
39+
set_max_latency::set_max_latency,
3840
set_min_pub::set_min_pub,
3941
upd_permissions::upd_permissions,
4042
upd_price::{
@@ -76,5 +78,6 @@ pub fn process_instruction(
7678
DelPrice => del_price(program_id, accounts, instruction_data),
7779
DelProduct => del_product(program_id, accounts, instruction_data),
7880
UpdPermissions => upd_permissions(program_id, accounts, instruction_data),
81+
SetMaxLatency => set_max_latency(program_id, accounts, instruction_data),
7982
}
8083
}

program/rust/src/processor/add_price.rs

+9-11
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ use {
1616
instruction::AddPriceArgs,
1717
utils::{
1818
check_exponent_range,
19+
check_permissioned_funding_account,
1920
check_valid_funding_account,
20-
check_valid_signable_account_or_permissioned_funding_account,
2121
pyth_assert,
2222
},
2323
OracleError,
@@ -48,26 +48,24 @@ pub fn add_price(
4848
)?;
4949

5050

51-
let (funding_account, product_account, price_account, permissions_account_option) =
52-
match accounts {
53-
[x, y, z] => Ok((x, y, z, None)),
54-
[x, y, z, p] => Ok((x, y, z, Some(p))),
55-
_ => Err(OracleError::InvalidNumberOfAccounts),
56-
}?;
51+
let (funding_account, product_account, price_account, permissions_account) = match accounts {
52+
[x, y, z, p] => Ok((x, y, z, p)),
53+
_ => Err(OracleError::InvalidNumberOfAccounts),
54+
}?;
5755

5856
check_valid_funding_account(funding_account)?;
59-
check_valid_signable_account_or_permissioned_funding_account(
57+
check_permissioned_funding_account(
6058
program_id,
6159
product_account,
6260
funding_account,
63-
permissions_account_option,
61+
permissions_account,
6462
&cmd_args.header,
6563
)?;
66-
check_valid_signable_account_or_permissioned_funding_account(
64+
check_permissioned_funding_account(
6765
program_id,
6866
price_account,
6967
funding_account,
70-
permissions_account_option,
68+
permissions_account,
7169
&cmd_args.header,
7270
)?;
7371

program/rust/src/processor/add_product.rs

+7-8
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ use {
1313
},
1414
instruction::CommandHeader,
1515
utils::{
16+
check_permissioned_funding_account,
1617
check_valid_funding_account,
17-
check_valid_signable_account_or_permissioned_funding_account,
1818
pyth_assert,
1919
try_convert,
2020
},
@@ -41,28 +41,27 @@ pub fn add_product(
4141
accounts: &[AccountInfo],
4242
instruction_data: &[u8],
4343
) -> ProgramResult {
44-
let (funding_account, tail_mapping_account, new_product_account, permissions_account_option) =
44+
let (funding_account, tail_mapping_account, new_product_account, permissions_account) =
4545
match accounts {
46-
[x, y, z] => Ok((x, y, z, None)),
47-
[x, y, z, p] => Ok((x, y, z, Some(p))),
46+
[x, y, z, p] => Ok((x, y, z, p)),
4847
_ => Err(OracleError::InvalidNumberOfAccounts),
4948
}?;
5049

5150
let hdr = load::<CommandHeader>(instruction_data)?;
5251

5352
check_valid_funding_account(funding_account)?;
54-
check_valid_signable_account_or_permissioned_funding_account(
53+
check_permissioned_funding_account(
5554
program_id,
5655
tail_mapping_account,
5756
funding_account,
58-
permissions_account_option,
57+
permissions_account,
5958
hdr,
6059
)?;
61-
check_valid_signable_account_or_permissioned_funding_account(
60+
check_permissioned_funding_account(
6261
program_id,
6362
new_product_account,
6463
funding_account,
65-
permissions_account_option,
64+
permissions_account,
6665
hdr,
6766
)?;
6867

program/rust/src/processor/add_publisher.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use {
1212
},
1313
instruction::AddPublisherArgs,
1414
utils::{
15+
check_permissioned_funding_account,
1516
check_valid_funding_account,
16-
check_valid_signable_account_or_permissioned_funding_account,
1717
pyth_assert,
1818
try_convert,
1919
},
@@ -46,18 +46,17 @@ pub fn add_publisher(
4646
ProgramError::InvalidArgument,
4747
)?;
4848

49-
let (funding_account, price_account, permissions_account_option) = match accounts {
50-
[x, y] => Ok((x, y, None)),
51-
[x, y, p] => Ok((x, y, Some(p))),
49+
let (funding_account, price_account, permissions_account) = match accounts {
50+
[x, y, p] => Ok((x, y, p)),
5251
_ => Err(OracleError::InvalidNumberOfAccounts),
5352
}?;
5453

5554
check_valid_funding_account(funding_account)?;
56-
check_valid_signable_account_or_permissioned_funding_account(
55+
check_permissioned_funding_account(
5756
program_id,
5857
price_account,
5958
funding_account,
60-
permissions_account_option,
59+
permissions_account,
6160
&cmd_args.header,
6261
)?;
6362

program/rust/src/processor/del_price.rs

+9-11
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ use {
1010
},
1111
instruction::CommandHeader,
1212
utils::{
13+
check_permissioned_funding_account,
1314
check_valid_funding_account,
14-
check_valid_signable_account_or_permissioned_funding_account,
1515
pyth_assert,
1616
},
1717
OracleError,
@@ -37,28 +37,26 @@ pub fn del_price(
3737
accounts: &[AccountInfo],
3838
instruction_data: &[u8],
3939
) -> ProgramResult {
40-
let (funding_account, product_account, price_account, permissions_account_option) =
41-
match accounts {
42-
[w, x, y] => Ok((w, x, y, None)),
43-
[w, x, y, p] => Ok((w, x, y, Some(p))),
44-
_ => Err(OracleError::InvalidNumberOfAccounts),
45-
}?;
40+
let (funding_account, product_account, price_account, permissions_account) = match accounts {
41+
[w, x, y, p] => Ok((w, x, y, p)),
42+
_ => Err(OracleError::InvalidNumberOfAccounts),
43+
}?;
4644

4745
let cmd_args = load::<CommandHeader>(instruction_data)?;
4846

4947
check_valid_funding_account(funding_account)?;
50-
check_valid_signable_account_or_permissioned_funding_account(
48+
check_permissioned_funding_account(
5149
program_id,
5250
product_account,
5351
funding_account,
54-
permissions_account_option,
52+
permissions_account,
5553
cmd_args,
5654
)?;
57-
check_valid_signable_account_or_permissioned_funding_account(
55+
check_permissioned_funding_account(
5856
program_id,
5957
price_account,
6058
funding_account,
61-
permissions_account_option,
59+
permissions_account,
6260
cmd_args,
6361
)?;
6462

0 commit comments

Comments
 (0)