Skip to content

Commit 4d15135

Browse files
jayantkguibescos
andauthored
Message Buffer CPI (#354)
* Checkpoint * use new-with-borsh * stuff * ok * fix instruction format (hopefully) * eliminate write * add logging * make data account writeable * add signing PDA and fix account list * no funding account * it works * should work * hopefully fix this script * whitespace * this s is important * pre-commit * no debug * add s back * reducing size * gr * minor edits * fix some stuff * use pythnet size * adding roundtrip test * forgot this file * update docs --------- Co-authored-by: Guillermo Bescos <[email protected]>
1 parent 614dfab commit 4d15135

File tree

13 files changed

+334
-29
lines changed

13 files changed

+334
-29
lines changed

.github/workflows/check-fomatting.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ jobs:
1414
- uses: actions-rs/toolchain@v1
1515
with:
1616
profile: minimal
17-
toolchain: nightly
17+
toolchain: nightly-2023-03-01
1818
components: rustfmt, clippy
1919
- uses: pre-commit/[email protected]

.pre-commit-config.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ repos:
99
- id: check-added-large-files
1010
- repo: local
1111
hooks:
12-
- id: cargo-fmt-nightly
13-
name: Cargo Fmt Nightly
12+
- id: cargo-fmt
13+
name: Cargo Fmt
1414
language: "rust"
15-
entry: cargo +nightly fmt
15+
entry: cargo +nightly-2023-03-01 fmt
1616
pass_filenames: false
1717
- id: cargo-clippy
1818
name: Cargo clippy
1919
language: "rust"
20-
entry : cargo +nightly clippy --tests -- -D warnings
20+
entry : cargo +nightly-2023-03-01 clippy --tests -- -D warnings
2121
pass_filenames : false

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
members = [
33
"program/rust"
44
]
5+

program/rust/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ num-derive = "0.3"
1616
num-traits = "0.2"
1717

1818
[dev-dependencies]
19+
byteorder = "1.4.3"
1920
solana-program-test = "=1.13.3"
2021
solana-sdk = "=1.13.3"
2122
tokio = "1.14.1"
@@ -29,6 +30,7 @@ serde_json = "1.0"
2930
test-generator = "0.3.1"
3031
csv = "1.1"
3132

33+
3234
[features]
3335
debug = []
3436
library = []

program/rust/src/accounts.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,12 @@ pub use product::{
4343
};
4444
pub use {
4545
mapping::MappingAccount,
46-
permission::{
47-
PermissionAccount,
48-
PERMISSIONS_SEED,
49-
},
46+
permission::PermissionAccount,
5047
price::{
5148
PriceAccount,
5249
PriceComponent,
5350
PriceEma,
51+
PriceFeedMessage,
5452
PriceInfo,
5553
},
5654
product::{
@@ -60,6 +58,15 @@ pub use {
6058
},
6159
};
6260

61+
// PDA seeds for accounts.
62+
/// There is a single permissions account under `PERMISSIONS_SEED` that stores which keys
63+
/// are authorized to perform certain adminsitrative actions.
64+
pub const PERMISSIONS_SEED: &str = "permissions";
65+
/// The update price instruction can optionally invoke another program via CPI. The
66+
/// CPI will be signed with the PDA `[UPD_PRICE_WRITE_SEED, invoked_program_public_key]`
67+
/// such that the caller can authenticate its origin.
68+
pub const UPD_PRICE_WRITE_SEED: &str = "upd_price_write";
69+
6370
#[repr(C)]
6471
#[derive(Copy, Clone, Zeroable, Pod)]
6572
pub struct AccountHeader {

program/rust/src/accounts/permission.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ use {
1515
std::mem::size_of,
1616
};
1717

18-
pub const PERMISSIONS_SEED: &str = "permissions";
19-
2018
/// This account stores the pubkeys that can execute administrative instructions in the Pyth
2119
/// program. Only the upgrade authority of the program can update these permissions.
2220
#[repr(C)]

program/rust/src/accounts/price.rs

Lines changed: 104 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use {
77
PC_ACCTYPE_PRICE,
88
PC_COMP_SIZE,
99
PC_PRICE_T_COMP_OFFSET,
10+
PC_STATUS_TRADING,
1011
},
1112
bytemuck::{
1213
Pod,
@@ -39,7 +40,9 @@ pub struct PriceAccount {
3940
pub timestamp_: i64,
4041
/// Minimum valid publisher quotes for a succesful aggregation
4142
pub min_pub_: u8,
42-
pub unused_1_: i8,
43+
/// Whether the current aggregate price has been sent as a message to the message buffer.
44+
/// 0 = false, 1 = true. (this is a u8 to make the Pod trait happy)
45+
pub message_sent_: u8,
4346
pub unused_2_: i16,
4447
pub unused_3_: i32,
4548
/// Corresponding product account
@@ -69,7 +72,7 @@ pub struct PriceComponent {
6972
}
7073

7174
#[repr(C)]
72-
#[derive(Debug, Copy, Clone, Pod, Zeroable)]
75+
#[derive(Copy, Clone, Pod, Zeroable)]
7376
pub struct PriceInfo {
7477
pub price_: i64,
7578
pub conf_: u64,
@@ -79,7 +82,7 @@ pub struct PriceInfo {
7982
}
8083

8184
#[repr(C)]
82-
#[derive(Debug, Copy, Clone, Pod, Zeroable)]
85+
#[derive(Copy, Clone, Pod, Zeroable)]
8386
pub struct PriceEma {
8487
pub val_: i64,
8588
pub numer_: i64,
@@ -91,3 +94,101 @@ impl PythAccount for PriceAccount {
9194
/// Equal to the offset of `comp_` in `PriceAccount`, see the trait comment for more detail
9295
const INITIAL_SIZE: u32 = PC_PRICE_T_COMP_OFFSET as u32;
9396
}
97+
98+
/// Message format for sending data to other chains via the accumulator program
99+
/// When serialized, each message starts with a unique 1-byte discriminator, followed by the
100+
/// serialized struct data in the definition(s) below.
101+
///
102+
/// Messages are forward-compatible. You may add new fields to messages after all previously
103+
/// defined fields. All code for parsing messages must ignore any extraneous bytes at the end of
104+
/// the message (which could be fields that the code does not yet understand).
105+
#[repr(C)]
106+
#[derive(Debug, Copy, Clone, PartialEq)]
107+
pub struct PriceFeedMessage {
108+
pub id: [u8; 32],
109+
pub price: i64,
110+
pub conf: u64,
111+
pub exponent: i32,
112+
/// The timestamp of this price update in seconds
113+
pub publish_time: i64,
114+
/// The timestamp of the previous price update. This field is intended to allow users to
115+
/// identify the single unique price update for any moment in time:
116+
/// for any time t, the unique update is the one such that prev_publish_time < t <= publish_time.
117+
///
118+
/// Note that there may not be such an update while we are migrating to the new message-sending logic,
119+
/// as some price updates on pythnet may not be sent to other chains (because the message-sending
120+
/// logic may not have triggered). We can solve this problem by making the message-sending mandatory
121+
/// (which we can do once publishers have migrated over).
122+
pub prev_publish_time: i64,
123+
pub ema_price: i64,
124+
pub ema_conf: u64,
125+
}
126+
127+
impl PriceFeedMessage {
128+
// The size of the serialized message. Note that this is not the same as the size of the struct
129+
// (because of the discriminator & struct padding/alignment).
130+
pub const MESSAGE_SIZE: usize = 1 + 32 + 8 + 8 + 4 + 8 + 8 + 8 + 8;
131+
pub const DISCRIMINATOR: u8 = 0;
132+
133+
pub fn from_price_account(key: &Pubkey, account: &PriceAccount) -> Self {
134+
let (price, conf, publish_time) = if account.agg_.status_ == PC_STATUS_TRADING {
135+
(account.agg_.price_, account.agg_.conf_, account.timestamp_)
136+
} else {
137+
(
138+
account.prev_price_,
139+
account.prev_conf_,
140+
account.prev_timestamp_,
141+
)
142+
};
143+
144+
Self {
145+
id: key.to_bytes(),
146+
price,
147+
conf,
148+
exponent: account.exponent,
149+
publish_time,
150+
prev_publish_time: account.prev_timestamp_,
151+
ema_price: account.twap_.val_,
152+
ema_conf: account.twac_.val_ as u64,
153+
}
154+
}
155+
156+
/// Serialize this message as an array of bytes (including the discriminator)
157+
/// Note that it would be more idiomatic to return a `Vec`, but that approach adds
158+
/// to the size of the compiled binary (which is already close to the size limit).
159+
#[allow(unused_assignments)]
160+
pub fn as_bytes(&self) -> [u8; PriceFeedMessage::MESSAGE_SIZE] {
161+
let mut bytes = [0u8; PriceFeedMessage::MESSAGE_SIZE];
162+
163+
let mut i: usize = 0;
164+
165+
bytes[i..i + 1].clone_from_slice(&[PriceFeedMessage::DISCRIMINATOR]);
166+
i += 1;
167+
168+
bytes[i..i + 32].clone_from_slice(&self.id[..]);
169+
i += 32;
170+
171+
bytes[i..i + 8].clone_from_slice(&self.price.to_be_bytes());
172+
i += 8;
173+
174+
bytes[i..i + 8].clone_from_slice(&self.conf.to_be_bytes());
175+
i += 8;
176+
177+
bytes[i..i + 4].clone_from_slice(&self.exponent.to_be_bytes());
178+
i += 4;
179+
180+
bytes[i..i + 8].clone_from_slice(&self.publish_time.to_be_bytes());
181+
i += 8;
182+
183+
bytes[i..i + 8].clone_from_slice(&self.prev_publish_time.to_be_bytes());
184+
i += 8;
185+
186+
bytes[i..i + 8].clone_from_slice(&self.ema_price.to_be_bytes());
187+
i += 8;
188+
189+
bytes[i..i + 8].clone_from_slice(&self.ema_conf.to_be_bytes());
190+
i += 8;
191+
192+
bytes
193+
}
194+
}

0 commit comments

Comments
 (0)