wormhole-attester: Add a previous attestation timestamp field (#488)
* wormhole-attester: Add a previous attestation timestamp field This change bumps price batch format to v3.1 with a new backwards compatible field - prev_attestation_time. This is the last time we've successfully attested the price. If no prior record exists, the current time is used (the same as attestation_time). The new field is backed by a new PDA for the attester contract, called 'attestation state'. In this PDA, we store a Pubkey -> Metadata hashmap for every price. Currently, the metadata stores just the latest successful attestation timestamp for use with the new field. * wormhole-attester: Use publish_time instead of attestation_time * wormhole_attester: use prev_publish_time for non-trading prices
This commit is contained in:
parent
1f4c0ba9cc
commit
7202b9339e
|
@ -12,9 +12,12 @@ pub use pyth_sdk::{
|
||||||
UnixTimestamp,
|
UnixTimestamp,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "solana")]
|
#[cfg(feature = "solana")]
|
||||||
use solitaire::{
|
use {
|
||||||
Derive,
|
pyth_sdk_solana::state::PriceAccount,
|
||||||
Info,
|
solitaire::{
|
||||||
|
Derive,
|
||||||
|
Info,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use {
|
use {
|
||||||
serde::{
|
serde::{
|
||||||
|
@ -47,12 +50,18 @@ pub const P2W_MAGIC: &[u8] = b"P2WH";
|
||||||
/// Format version used and understood by this codebase
|
/// Format version used and understood by this codebase
|
||||||
pub const P2W_FORMAT_VER_MAJOR: u16 = 3;
|
pub const P2W_FORMAT_VER_MAJOR: u16 = 3;
|
||||||
|
|
||||||
/// Starting with v3, format introduces a minor version to mark forward-compatible iterations
|
/// Starting with v3, format introduces a minor version to mark
|
||||||
pub const P2W_FORMAT_VER_MINOR: u16 = 0;
|
/// forward-compatible iterations.
|
||||||
|
/// IMPORTANT: Remember to reset this to 0 whenever major version is
|
||||||
|
/// bumped.
|
||||||
|
/// Changelog:
|
||||||
|
/// * v3.1 - last_attested_publish_time field added
|
||||||
|
pub const P2W_FORMAT_VER_MINOR: u16 = 1;
|
||||||
|
|
||||||
/// Starting with v3, format introduces append-only
|
/// Starting with v3, format introduces append-only
|
||||||
/// forward-compatibility to the header. This is the current number of
|
/// forward-compatibility to the header. This is the current number of
|
||||||
/// bytes after the hdr_size field.
|
/// bytes after the hdr_size field. After the specified bytes, inner
|
||||||
|
/// payload-specific fields begin.
|
||||||
pub const P2W_FORMAT_HDR_SIZE: u16 = 1;
|
pub const P2W_FORMAT_HDR_SIZE: u16 = 1;
|
||||||
|
|
||||||
pub const PUBKEY_LEN: usize = 32;
|
pub const PUBKEY_LEN: usize = 32;
|
||||||
|
@ -80,28 +89,29 @@ pub enum PayloadId {
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct PriceAttestation {
|
pub struct PriceAttestation {
|
||||||
#[serde(serialize_with = "pubkey_to_hex")]
|
#[serde(serialize_with = "pubkey_to_hex")]
|
||||||
pub product_id: Identifier,
|
pub product_id: Identifier,
|
||||||
#[serde(serialize_with = "pubkey_to_hex")]
|
#[serde(serialize_with = "pubkey_to_hex")]
|
||||||
pub price_id: Identifier,
|
pub price_id: Identifier,
|
||||||
#[serde(serialize_with = "use_to_string")]
|
#[serde(serialize_with = "use_to_string")]
|
||||||
pub price: i64,
|
pub price: i64,
|
||||||
#[serde(serialize_with = "use_to_string")]
|
#[serde(serialize_with = "use_to_string")]
|
||||||
pub conf: u64,
|
pub conf: u64,
|
||||||
pub expo: i32,
|
pub expo: i32,
|
||||||
#[serde(serialize_with = "use_to_string")]
|
#[serde(serialize_with = "use_to_string")]
|
||||||
pub ema_price: i64,
|
pub ema_price: i64,
|
||||||
#[serde(serialize_with = "use_to_string")]
|
#[serde(serialize_with = "use_to_string")]
|
||||||
pub ema_conf: u64,
|
pub ema_conf: u64,
|
||||||
pub status: PriceStatus,
|
pub status: PriceStatus,
|
||||||
pub num_publishers: u32,
|
pub num_publishers: u32,
|
||||||
pub max_num_publishers: u32,
|
pub max_num_publishers: u32,
|
||||||
pub attestation_time: UnixTimestamp,
|
pub attestation_time: UnixTimestamp,
|
||||||
pub publish_time: UnixTimestamp,
|
pub publish_time: UnixTimestamp,
|
||||||
pub prev_publish_time: UnixTimestamp,
|
pub prev_publish_time: UnixTimestamp,
|
||||||
#[serde(serialize_with = "use_to_string")]
|
#[serde(serialize_with = "use_to_string")]
|
||||||
pub prev_price: i64,
|
pub prev_price: i64,
|
||||||
#[serde(serialize_with = "use_to_string")]
|
#[serde(serialize_with = "use_to_string")]
|
||||||
pub prev_conf: u64,
|
pub prev_conf: u64,
|
||||||
|
pub last_attested_publish_time: UnixTimestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper allowing ToString implementers to be serialized as strings accordingly
|
/// Helper allowing ToString implementers to be serialized as strings accordingly
|
||||||
|
@ -146,6 +156,10 @@ impl BatchPriceAttestation {
|
||||||
// payload_id
|
// payload_id
|
||||||
buf.push(PayloadId::PriceBatchAttestation as u8);
|
buf.push(PayloadId::PriceBatchAttestation as u8);
|
||||||
|
|
||||||
|
// Header is over. NOTE: If you need to append to the header,
|
||||||
|
// make sure that the number of bytes after hdr_size is
|
||||||
|
// reflected in the P2W_FORMAT_HDR_SIZE constant.
|
||||||
|
|
||||||
// n_attestations
|
// n_attestations
|
||||||
buf.extend_from_slice(&(self.price_attestations.len() as u16).to_be_bytes()[..]);
|
buf.extend_from_slice(&(self.price_attestations.len() as u16).to_be_bytes()[..]);
|
||||||
|
|
||||||
|
@ -279,11 +293,25 @@ impl PriceAttestation {
|
||||||
pub fn from_pyth_price_bytes(
|
pub fn from_pyth_price_bytes(
|
||||||
price_id: Identifier,
|
price_id: Identifier,
|
||||||
attestation_time: UnixTimestamp,
|
attestation_time: UnixTimestamp,
|
||||||
|
last_attested_publish_time: UnixTimestamp,
|
||||||
value: &[u8],
|
value: &[u8],
|
||||||
) -> Result<Self, ErrBox> {
|
) -> Result<Self, ErrBox> {
|
||||||
let price = pyth_sdk_solana::state::load_price_account(value)?;
|
let price_struct = pyth_sdk_solana::state::load_price_account(value)?;
|
||||||
|
Ok(Self::from_pyth_price_struct(
|
||||||
Ok(PriceAttestation {
|
price_id,
|
||||||
|
attestation_time,
|
||||||
|
last_attested_publish_time,
|
||||||
|
price_struct,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "solana")]
|
||||||
|
pub fn from_pyth_price_struct(
|
||||||
|
price_id: Identifier,
|
||||||
|
attestation_time: UnixTimestamp,
|
||||||
|
last_attested_publish_time: UnixTimestamp,
|
||||||
|
price: &PriceAccount,
|
||||||
|
) -> Self {
|
||||||
|
PriceAttestation {
|
||||||
product_id: Identifier::new(price.prod.val),
|
product_id: Identifier::new(price.prod.val),
|
||||||
price_id,
|
price_id,
|
||||||
price: price.agg.price,
|
price: price.agg.price,
|
||||||
|
@ -299,7 +327,8 @@ impl PriceAttestation {
|
||||||
prev_publish_time: price.prev_timestamp,
|
prev_publish_time: price.prev_timestamp,
|
||||||
prev_price: price.prev_price,
|
prev_price: price.prev_price,
|
||||||
prev_conf: price.prev_conf,
|
prev_conf: price.prev_conf,
|
||||||
})
|
last_attested_publish_time,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serialize this attestation according to the Pyth-over-wormhole serialization format
|
/// Serialize this attestation according to the Pyth-over-wormhole serialization format
|
||||||
|
@ -322,6 +351,7 @@ impl PriceAttestation {
|
||||||
prev_publish_time,
|
prev_publish_time,
|
||||||
prev_price,
|
prev_price,
|
||||||
prev_conf,
|
prev_conf,
|
||||||
|
last_attested_publish_time,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
|
@ -371,6 +401,9 @@ impl PriceAttestation {
|
||||||
// prev_conf
|
// prev_conf
|
||||||
buf.extend_from_slice(&prev_conf.to_be_bytes()[..]);
|
buf.extend_from_slice(&prev_conf.to_be_bytes()[..]);
|
||||||
|
|
||||||
|
// last_attested_publish_time
|
||||||
|
buf.extend_from_slice(&last_attested_publish_time.to_be_bytes()[..]);
|
||||||
|
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
pub fn deserialize(mut bytes: impl Read) -> Result<Self, ErrBox> {
|
pub fn deserialize(mut bytes: impl Read) -> Result<Self, ErrBox> {
|
||||||
|
@ -444,6 +477,11 @@ impl PriceAttestation {
|
||||||
bytes.read_exact(prev_conf_vec.as_mut_slice())?;
|
bytes.read_exact(prev_conf_vec.as_mut_slice())?;
|
||||||
let prev_conf = u64::from_be_bytes(prev_conf_vec.as_slice().try_into()?);
|
let prev_conf = u64::from_be_bytes(prev_conf_vec.as_slice().try_into()?);
|
||||||
|
|
||||||
|
let mut last_attested_publish_time_vec = vec![0u8; mem::size_of::<UnixTimestamp>()];
|
||||||
|
bytes.read_exact(last_attested_publish_time_vec.as_mut_slice())?;
|
||||||
|
let last_attested_publish_time =
|
||||||
|
UnixTimestamp::from_be_bytes(last_attested_publish_time_vec.as_slice().try_into()?);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
product_id,
|
product_id,
|
||||||
price_id,
|
price_id,
|
||||||
|
@ -460,6 +498,7 @@ impl PriceAttestation {
|
||||||
prev_publish_time,
|
prev_publish_time,
|
||||||
prev_price,
|
prev_price,
|
||||||
prev_conf,
|
prev_conf,
|
||||||
|
last_attested_publish_time,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -478,21 +517,22 @@ mod tests {
|
||||||
let product_id_bytes = prod.unwrap_or([21u8; 32]);
|
let product_id_bytes = prod.unwrap_or([21u8; 32]);
|
||||||
let price_id_bytes = price.unwrap_or([222u8; 32]);
|
let price_id_bytes = price.unwrap_or([222u8; 32]);
|
||||||
PriceAttestation {
|
PriceAttestation {
|
||||||
product_id: Identifier::new(product_id_bytes),
|
product_id: Identifier::new(product_id_bytes),
|
||||||
price_id: Identifier::new(price_id_bytes),
|
price_id: Identifier::new(price_id_bytes),
|
||||||
price: 0x2bad2feed7,
|
price: 0x2bad2feed7,
|
||||||
conf: 101,
|
conf: 101,
|
||||||
ema_price: -42,
|
ema_price: -42,
|
||||||
ema_conf: 42,
|
ema_conf: 42,
|
||||||
expo: -3,
|
expo: -3,
|
||||||
status: PriceStatus::Trading,
|
status: PriceStatus::Trading,
|
||||||
num_publishers: 123212u32,
|
num_publishers: 123212u32,
|
||||||
max_num_publishers: 321232u32,
|
max_num_publishers: 321232u32,
|
||||||
attestation_time: (0xdeadbeeffadedeedu64) as i64,
|
attestation_time: (0xdeadbeeffadedeedu64) as i64,
|
||||||
publish_time: 0xdadebeefi64,
|
publish_time: 0xdadebeefi64,
|
||||||
prev_publish_time: 0xdeadbabei64,
|
prev_publish_time: 0xdeadbabei64,
|
||||||
prev_price: 0xdeadfacebeefi64,
|
prev_price: 0xdeadfacebeefi64,
|
||||||
prev_conf: 0xbadbadbeefu64, // I could do this all day -SD
|
prev_conf: 0xbadbadbeefu64, // I could do this all day -SD
|
||||||
|
last_attested_publish_time: (0xdeadbeeffadedeafu64) as i64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2699,6 +2699,7 @@ dependencies = [
|
||||||
"borsh",
|
"borsh",
|
||||||
"p2w-sdk",
|
"p2w-sdk",
|
||||||
"pyth-client",
|
"pyth-client",
|
||||||
|
"pyth-sdk-solana 0.5.0",
|
||||||
"rocksalt",
|
"rocksalt",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
|
|
|
@ -49,6 +49,7 @@ use {
|
||||||
load_product_account,
|
load_product_account,
|
||||||
},
|
},
|
||||||
pyth_wormhole_attester::{
|
pyth_wormhole_attester::{
|
||||||
|
attestation_state::AttestationStateMapPDA,
|
||||||
config::{
|
config::{
|
||||||
OldP2WConfigAccount,
|
OldP2WConfigAccount,
|
||||||
P2WConfigAccount,
|
P2WConfigAccount,
|
||||||
|
@ -324,6 +325,8 @@ pub fn gen_attest_tx(
|
||||||
AccountMeta::new_readonly(system_program::id(), false),
|
AccountMeta::new_readonly(system_program::id(), false),
|
||||||
// config
|
// config
|
||||||
AccountMeta::new_readonly(p2w_config_addr, false),
|
AccountMeta::new_readonly(p2w_config_addr, false),
|
||||||
|
// attestation_state
|
||||||
|
AccountMeta::new(AttestationStateMapPDA::key(None, &p2w_addr), false),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Batch contents and padding if applicable
|
// Batch contents and padding if applicable
|
||||||
|
|
|
@ -25,3 +25,4 @@ p2w-sdk = { path = "../../third_party/pyth/p2w-sdk/rust", features = ["solana"]
|
||||||
serde = { version = "1", optional = true}
|
serde = { version = "1", optional = true}
|
||||||
serde_derive = { version = "1", optional = true}
|
serde_derive = { version = "1", optional = true}
|
||||||
serde_json = { version = "1", optional = true}
|
serde_json = { version = "1", optional = true}
|
||||||
|
pyth-sdk-solana = { version = "0.5.0" }
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
use {
|
use {
|
||||||
crate::{
|
crate::{
|
||||||
|
attestation_state::{
|
||||||
|
AttestationState,
|
||||||
|
AttestationStateMapPDA,
|
||||||
|
},
|
||||||
config::P2WConfigAccount,
|
config::P2WConfigAccount,
|
||||||
message::{
|
message::{
|
||||||
P2WMessage,
|
P2WMessage,
|
||||||
|
@ -20,6 +24,7 @@ use {
|
||||||
P2WEmitter,
|
P2WEmitter,
|
||||||
PriceAttestation,
|
PriceAttestation,
|
||||||
},
|
},
|
||||||
|
pyth_sdk_solana::state::PriceStatus,
|
||||||
solana_program::{
|
solana_program::{
|
||||||
clock::Clock,
|
clock::Clock,
|
||||||
program::{
|
program::{
|
||||||
|
@ -34,6 +39,7 @@ use {
|
||||||
solitaire::{
|
solitaire::{
|
||||||
trace,
|
trace,
|
||||||
AccountState,
|
AccountState,
|
||||||
|
CreationLamports,
|
||||||
ExecutionContext,
|
ExecutionContext,
|
||||||
FromAccounts,
|
FromAccounts,
|
||||||
Info,
|
Info,
|
||||||
|
@ -60,9 +66,10 @@ pub const P2W_MAX_BATCH_SIZE: u16 = 5;
|
||||||
#[derive(FromAccounts)]
|
#[derive(FromAccounts)]
|
||||||
pub struct Attest<'b> {
|
pub struct Attest<'b> {
|
||||||
// Payer also used for wormhole
|
// Payer also used for wormhole
|
||||||
pub payer: Mut<Signer<Info<'b>>>,
|
pub payer: Mut<Signer<Info<'b>>>,
|
||||||
pub system_program: Info<'b>,
|
pub system_program: Info<'b>,
|
||||||
pub config: P2WConfigAccount<'b, { AccountState::Initialized }>,
|
pub config: P2WConfigAccount<'b, { AccountState::Initialized }>,
|
||||||
|
pub attestation_state: Mut<AttestationStateMapPDA<'b>>,
|
||||||
|
|
||||||
// Hardcoded product/price pairs, bypassing Solitaire's variable-length limitations
|
// Hardcoded product/price pairs, bypassing Solitaire's variable-length limitations
|
||||||
// Any change to the number of accounts must include an appropriate change to P2W_MAX_BATCH_SIZE
|
// Any change to the number of accounts must include an appropriate change to P2W_MAX_BATCH_SIZE
|
||||||
|
@ -152,6 +159,7 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
|
||||||
return Err(ProgramError::InvalidAccountData.into());
|
return Err(ProgramError::InvalidAccountData.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Make the specified prices iterable
|
// Make the specified prices iterable
|
||||||
let price_pair_opts = [
|
let price_pair_opts = [
|
||||||
Some(&accs.pyth_product),
|
Some(&accs.pyth_product),
|
||||||
|
@ -204,16 +212,48 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
|
||||||
));
|
));
|
||||||
return Err(SolitaireError::InvalidOwner(*accs.pyth_price.owner));
|
return Err(SolitaireError::InvalidOwner(*accs.pyth_price.owner));
|
||||||
}
|
}
|
||||||
|
let attestation_time = accs.clock.unix_timestamp;
|
||||||
|
|
||||||
let attestation = PriceAttestation::from_pyth_price_bytes(
|
let price_data_ref = price.try_borrow_data()?;
|
||||||
|
|
||||||
|
// Parse the upstream Pyth struct to extract current publish
|
||||||
|
// time for payload construction
|
||||||
|
let price_struct =
|
||||||
|
pyth_sdk_solana::state::load_price_account(&price_data_ref).map_err(|e| {
|
||||||
|
trace!(&e.to_string());
|
||||||
|
ProgramError::InvalidAccountData
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// prev_publish_time is picked if the price is not trading
|
||||||
|
let last_trading_publish_time = match price_struct.agg.status {
|
||||||
|
PriceStatus::Trading => price_struct.timestamp,
|
||||||
|
_ => price_struct.prev_timestamp,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Take a mut reference to this price's metadata
|
||||||
|
let state_entry: &mut AttestationState = accs
|
||||||
|
.attestation_state
|
||||||
|
.entries
|
||||||
|
.entry(*price.key)
|
||||||
|
.or_insert(AttestationState {
|
||||||
|
// Use the same value if no state
|
||||||
|
// exists for the symbol, the new value _becomes_ the
|
||||||
|
// last attested trading publish time
|
||||||
|
last_attested_trading_publish_time: last_trading_publish_time,
|
||||||
|
});
|
||||||
|
|
||||||
|
let attestation = PriceAttestation::from_pyth_price_struct(
|
||||||
Identifier::new(price.key.to_bytes()),
|
Identifier::new(price.key.to_bytes()),
|
||||||
accs.clock.unix_timestamp,
|
attestation_time,
|
||||||
&price.try_borrow_data()?,
|
state_entry.last_attested_trading_publish_time, // Used as last_attested_publish_time
|
||||||
)
|
price_struct,
|
||||||
.map_err(|e| {
|
);
|
||||||
trace!(&e.to_string());
|
|
||||||
ProgramError::InvalidAccountData
|
|
||||||
})?;
|
// update last_attested_publish_time with this price's
|
||||||
|
// publish_time. Yes, it may be redundant for the entry() used
|
||||||
|
// above in the rare first attestation edge case.
|
||||||
|
state_entry.last_attested_trading_publish_time = last_trading_publish_time;
|
||||||
|
|
||||||
// The following check is crucial against poorly ordered
|
// The following check is crucial against poorly ordered
|
||||||
// account inputs, e.g. [Some(prod1), Some(price1),
|
// account inputs, e.g. [Some(prod1), Some(price1),
|
||||||
|
@ -240,6 +280,51 @@ pub fn attest(ctx: &ExecutionContext, accs: &mut Attest, data: AttestData) -> So
|
||||||
|
|
||||||
trace!("Attestations successfully created");
|
trace!("Attestations successfully created");
|
||||||
|
|
||||||
|
// Serialize the state to calculate rent/account size adjustments
|
||||||
|
let serialized = accs.attestation_state.1.try_to_vec()?;
|
||||||
|
|
||||||
|
if accs.attestation_state.is_initialized() {
|
||||||
|
accs.attestation_state
|
||||||
|
.info()
|
||||||
|
.realloc(serialized.len(), false)?;
|
||||||
|
trace!("Attestation state resize OK");
|
||||||
|
|
||||||
|
let target_rent = CreationLamports::Exempt.amount(serialized.len());
|
||||||
|
let current_rent = accs.attestation_state.info().lamports();
|
||||||
|
|
||||||
|
// Adjust rent, but only if there isn't enough
|
||||||
|
if target_rent > current_rent {
|
||||||
|
let transfer_amount = target_rent - current_rent;
|
||||||
|
|
||||||
|
let transfer_ix = system_instruction::transfer(
|
||||||
|
accs.payer.info().key,
|
||||||
|
accs.attestation_state.info().key,
|
||||||
|
transfer_amount,
|
||||||
|
);
|
||||||
|
|
||||||
|
invoke(&transfer_ix, ctx.accounts)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!("Attestation state rent transfer OK");
|
||||||
|
} else {
|
||||||
|
let seeds = accs
|
||||||
|
.attestation_state
|
||||||
|
.self_bumped_seeds(None, ctx.program_id);
|
||||||
|
solitaire::create_account(
|
||||||
|
ctx,
|
||||||
|
accs.attestation_state.info(),
|
||||||
|
accs.payer.key,
|
||||||
|
solitaire::CreationLamports::Exempt,
|
||||||
|
serialized.len(),
|
||||||
|
ctx.program_id,
|
||||||
|
solitaire::IsSigned::SignedWithSeeds(&[seeds
|
||||||
|
.iter()
|
||||||
|
.map(|s| s.as_slice())
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.as_slice()]),
|
||||||
|
)?;
|
||||||
|
trace!("Attestation state init OK");
|
||||||
|
}
|
||||||
let bridge_config = BridgeData::try_from_slice(&accs.wh_bridge.try_borrow_mut_data()?)?.config;
|
let bridge_config = BridgeData::try_from_slice(&accs.wh_bridge.try_borrow_mut_data()?)?.config;
|
||||||
|
|
||||||
// Pay wormhole fee
|
// Pay wormhole fee
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
//! Implementation of per-symbol on-chain state. Currently used to
|
||||||
|
//! store latest successful attestation time for each price.
|
||||||
|
|
||||||
|
use {
|
||||||
|
borsh::{
|
||||||
|
BorshDeserialize,
|
||||||
|
BorshSerialize,
|
||||||
|
},
|
||||||
|
solana_program::{
|
||||||
|
clock::UnixTimestamp,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
},
|
||||||
|
solitaire::{
|
||||||
|
AccountOwner,
|
||||||
|
AccountState,
|
||||||
|
Data,
|
||||||
|
Derive,
|
||||||
|
Owned,
|
||||||
|
},
|
||||||
|
std::collections::BTreeMap,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// On-chain state for a single price attestation
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize)]
|
||||||
|
pub struct AttestationState {
|
||||||
|
/// The last trading publish_time this attester saw
|
||||||
|
pub last_attested_trading_publish_time: UnixTimestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Top-level state gathering all known AttestationState values, keyed by price address.
|
||||||
|
#[derive(BorshSerialize, BorshDeserialize, Default)]
|
||||||
|
pub struct AttestationStateMap {
|
||||||
|
pub entries: BTreeMap<Pubkey, AttestationState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Owned for AttestationStateMap {
|
||||||
|
fn owner(&self) -> AccountOwner {
|
||||||
|
AccountOwner::This
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type AttestationStateMapPDA<'b> = Derive<
|
||||||
|
Data<'b, AttestationStateMap, { AccountState::MaybeInitialized }>,
|
||||||
|
"p2w-attestation-state-v1",
|
||||||
|
>;
|
|
@ -1,6 +1,7 @@
|
||||||
#![allow(incomplete_features)]
|
#![allow(incomplete_features)]
|
||||||
#![feature(adt_const_params)]
|
#![feature(adt_const_params)]
|
||||||
pub mod attest;
|
pub mod attest;
|
||||||
|
pub mod attestation_state;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod initialize;
|
pub mod initialize;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
|
|
|
@ -44,7 +44,7 @@ impl<'a> Seeded<&P2WMessageDrvData> for P2WMessage<'a> {
|
||||||
// See the note at 2022-09-05 above.
|
// See the note at 2022-09-05 above.
|
||||||
// Change the version in the literal whenever you change the
|
// Change the version in the literal whenever you change the
|
||||||
// price attestation data.
|
// price attestation data.
|
||||||
"p2w-message-v1".as_bytes().to_vec(),
|
"p2w-message-v2".as_bytes().to_vec(),
|
||||||
data.message_owner.to_bytes().to_vec(),
|
data.message_owner.to_bytes().to_vec(),
|
||||||
data.batch_size.to_be_bytes().to_vec(),
|
data.batch_size.to_be_bytes().to_vec(),
|
||||||
data.id.to_be_bytes().to_vec(),
|
data.id.to_be_bytes().to_vec(),
|
||||||
|
|
Loading…
Reference in New Issue