use {
crate::types::{AnchorDecimal, ProductGreeks},
carbon_core::{
account::AccountMetadata,
postgres::{
metadata::AccountRowMetadata,
primitives::{Pubkey, U64, U8},
},
},
};
#[derive(sqlx::FromRow, Debug, Clone)]
pub struct GreeksRow {
#[sqlx(flatten)]
pub account_metadata: AccountRowMetadata,
pub nonce: U8,
pub mark_prices: Vec<U64>,
pub mark_prices_padding: Vec<U64>,
pub perp_mark_price: U64,
pub product_greeks: sqlx::types::Json<Vec<ProductGreeks>>,
pub product_greeks_padding: sqlx::types::Json<Vec<ProductGreeks>>,
pub update_timestamp: Vec<U64>,
pub update_timestamp_padding: Vec<U64>,
pub retreat_expiration_timestamp: Vec<U64>,
pub retreat_expiration_timestamp_padding: Vec<U64>,
pub interest_rate: Vec<i64>,
pub interest_rate_padding: Vec<i64>,
pub nodes: Vec<U64>,
pub volatility: Vec<U64>,
pub volatility_padding: Vec<U64>,
pub node_keys: Vec<Pubkey>,
pub halt_force_pricing: Vec<bool>,
pub perp_update_timestamp: U64,
pub perp_funding_delta: sqlx::types::Json<AnchorDecimal>,
pub perp_latest_funding_rate: sqlx::types::Json<AnchorDecimal>,
pub perp_latest_midpoint: U64,
pub padding: Vec<u8>,
}
impl GreeksRow {
pub fn from_parts(source: crate::accounts::greeks::Greeks, metadata: AccountMetadata) -> Self {
Self {
account_metadata: metadata.into(),
nonce: source.nonce.into(),
mark_prices: source
.mark_prices
.into_iter()
.map(|element| element.into())
.collect(),
mark_prices_padding: source
.mark_prices_padding
.into_iter()
.map(|element| element.into())
.collect(),
perp_mark_price: source.perp_mark_price.into(),
product_greeks: sqlx::types::Json(source.product_greeks.to_vec()),
product_greeks_padding: sqlx::types::Json(source.product_greeks_padding.to_vec()),
update_timestamp: source
.update_timestamp
.into_iter()
.map(|element| element.into())
.collect(),
update_timestamp_padding: source
.update_timestamp_padding
.into_iter()
.map(|element| element.into())
.collect(),
retreat_expiration_timestamp: source
.retreat_expiration_timestamp
.into_iter()
.map(|element| element.into())
.collect(),
retreat_expiration_timestamp_padding: source
.retreat_expiration_timestamp_padding
.into_iter()
.map(|element| element.into())
.collect(),
interest_rate: source.interest_rate.to_vec(),
interest_rate_padding: source.interest_rate_padding.to_vec(),
nodes: source
.nodes
.into_iter()
.map(|element| element.into())
.collect(),
volatility: source
.volatility
.into_iter()
.map(|element| element.into())
.collect(),
volatility_padding: source
.volatility_padding
.into_iter()
.map(|element| element.into())
.collect(),
node_keys: source
.node_keys
.into_iter()
.map(|element| element.into())
.collect(),
halt_force_pricing: source.halt_force_pricing.to_vec(),
perp_update_timestamp: source.perp_update_timestamp.into(),
perp_funding_delta: sqlx::types::Json(source.perp_funding_delta),
perp_latest_funding_rate: sqlx::types::Json(source.perp_latest_funding_rate),
perp_latest_midpoint: source.perp_latest_midpoint.into(),
padding: source.padding.to_vec(),
}
}
}
impl TryFrom<GreeksRow> for crate::accounts::greeks::Greeks {
type Error = carbon_core::error::Error;
fn try_from(source: GreeksRow) -> Result<Self, Self::Error> {
Ok(Self {
nonce: source.nonce.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
mark_prices: source
.mark_prices
.into_iter()
.map(|element| Ok(*element))
.collect::<Result<Vec<_>, carbon_core::error::Error>>()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to collect array elements".to_string(),
)
})?
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert array element to primitive".to_string(),
)
})?,
mark_prices_padding: source
.mark_prices_padding
.into_iter()
.map(|element| Ok(*element))
.collect::<Result<Vec<_>, carbon_core::error::Error>>()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to collect array elements".to_string(),
)
})?
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert array element to primitive".to_string(),
)
})?,
perp_mark_price: *source.perp_mark_price,
product_greeks: source
.product_greeks
.0
.into_iter()
.collect::<Vec<_>>()
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
product_greeks_padding: source
.product_greeks_padding
.0
.into_iter()
.collect::<Vec<_>>()
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
update_timestamp: source
.update_timestamp
.into_iter()
.map(|element| Ok(*element))
.collect::<Result<Vec<_>, carbon_core::error::Error>>()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to collect array elements".to_string(),
)
})?
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert array element to primitive".to_string(),
)
})?,
update_timestamp_padding: source
.update_timestamp_padding
.into_iter()
.map(|element| Ok(*element))
.collect::<Result<Vec<_>, carbon_core::error::Error>>()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to collect array elements".to_string(),
)
})?
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert array element to primitive".to_string(),
)
})?,
retreat_expiration_timestamp: source
.retreat_expiration_timestamp
.into_iter()
.map(|element| Ok(*element))
.collect::<Result<Vec<_>, carbon_core::error::Error>>()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to collect array elements".to_string(),
)
})?
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert array element to primitive".to_string(),
)
})?,
retreat_expiration_timestamp_padding: source
.retreat_expiration_timestamp_padding
.into_iter()
.map(|element| Ok(*element))
.collect::<Result<Vec<_>, carbon_core::error::Error>>()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to collect array elements".to_string(),
)
})?
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert array element to primitive".to_string(),
)
})?,
interest_rate: source
.interest_rate
.into_iter()
.map(Ok)
.collect::<Result<Vec<_>, carbon_core::error::Error>>()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to collect array elements".to_string(),
)
})?
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert array element to primitive".to_string(),
)
})?,
interest_rate_padding: source
.interest_rate_padding
.into_iter()
.map(Ok)
.collect::<Result<Vec<_>, carbon_core::error::Error>>()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to collect array elements".to_string(),
)
})?
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert array element to primitive".to_string(),
)
})?,
nodes: source
.nodes
.into_iter()
.map(|element| Ok(*element))
.collect::<Result<Vec<_>, carbon_core::error::Error>>()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to collect array elements".to_string(),
)
})?
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert array element to primitive".to_string(),
)
})?,
volatility: source
.volatility
.into_iter()
.map(|element| Ok(*element))
.collect::<Result<Vec<_>, carbon_core::error::Error>>()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to collect array elements".to_string(),
)
})?
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert array element to primitive".to_string(),
)
})?,
volatility_padding: source
.volatility_padding
.into_iter()
.map(|element| Ok(*element))
.collect::<Result<Vec<_>, carbon_core::error::Error>>()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to collect array elements".to_string(),
)
})?
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert array element to primitive".to_string(),
)
})?,
node_keys: source
.node_keys
.into_iter()
.map(|element| Ok(*element))
.collect::<Result<Vec<_>, carbon_core::error::Error>>()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to collect array elements".to_string(),
)
})?
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert array element to primitive".to_string(),
)
})?,
halt_force_pricing: source
.halt_force_pricing
.into_iter()
.map(Ok)
.collect::<Result<Vec<_>, carbon_core::error::Error>>()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to collect array elements".to_string(),
)
})?
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert array element to primitive".to_string(),
)
})?,
perp_update_timestamp: *source.perp_update_timestamp,
perp_funding_delta: source.perp_funding_delta.0,
perp_latest_funding_rate: source.perp_latest_funding_rate.0,
perp_latest_midpoint: *source.perp_latest_midpoint,
padding: source.padding.as_slice().try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert padding from postgres primitive: expected 1593 bytes"
.to_string(),
)
})?,
})
}
}
impl carbon_core::postgres::operations::Table for crate::accounts::greeks::Greeks {
fn table() -> &'static str {
"greeks_account"
}
fn columns() -> Vec<&'static str> {
vec![
"__pubkey",
"__slot",
"nonce",
"mark_prices",
"mark_prices_padding",
"perp_mark_price",
"product_greeks",
"product_greeks_padding",
"update_timestamp",
"update_timestamp_padding",
"retreat_expiration_timestamp",
"retreat_expiration_timestamp_padding",
"interest_rate",
"interest_rate_padding",
"nodes",
"volatility",
"volatility_padding",
"node_keys",
"halt_force_pricing",
"perp_update_timestamp",
"perp_funding_delta",
"perp_latest_funding_rate",
"perp_latest_midpoint",
"padding",
]
}
}
#[async_trait::async_trait]
impl carbon_core::postgres::operations::Insert for GreeksRow {
async fn insert(&self, pool: &sqlx::PgPool) -> carbon_core::error::CarbonResult<()> {
sqlx::query(r#"
INSERT INTO greeks_account (
"nonce",
"mark_prices",
"mark_prices_padding",
"perp_mark_price",
"product_greeks",
"product_greeks_padding",
"update_timestamp",
"update_timestamp_padding",
"retreat_expiration_timestamp",
"retreat_expiration_timestamp_padding",
"interest_rate",
"interest_rate_padding",
"nodes",
"volatility",
"volatility_padding",
"node_keys",
"halt_force_pricing",
"perp_update_timestamp",
"perp_funding_delta",
"perp_latest_funding_rate",
"perp_latest_midpoint",
"padding",
__pubkey, __slot
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24
)"#)
.bind(self.nonce)
.bind(&self.mark_prices)
.bind(&self.mark_prices_padding)
.bind(&self.perp_mark_price)
.bind(&self.product_greeks)
.bind(&self.product_greeks_padding)
.bind(&self.update_timestamp)
.bind(&self.update_timestamp_padding)
.bind(&self.retreat_expiration_timestamp)
.bind(&self.retreat_expiration_timestamp_padding)
.bind(&self.interest_rate)
.bind(&self.interest_rate_padding)
.bind(&self.nodes)
.bind(&self.volatility)
.bind(&self.volatility_padding)
.bind(&self.node_keys)
.bind(&self.halt_force_pricing)
.bind(&self.perp_update_timestamp)
.bind(&self.perp_funding_delta)
.bind(&self.perp_latest_funding_rate)
.bind(&self.perp_latest_midpoint)
.bind(&self.padding)
.bind(self.account_metadata.pubkey)
.bind(&self.account_metadata.slot)
.execute(pool).await
.map_err(|e| carbon_core::error::Error::Custom(e.to_string()))?;
Ok(())
}
}
#[async_trait::async_trait]
impl carbon_core::postgres::operations::Upsert for GreeksRow {
async fn upsert(&self, pool: &sqlx::PgPool) -> carbon_core::error::CarbonResult<()> {
sqlx::query(r#"INSERT INTO greeks_account (
"nonce",
"mark_prices",
"mark_prices_padding",
"perp_mark_price",
"product_greeks",
"product_greeks_padding",
"update_timestamp",
"update_timestamp_padding",
"retreat_expiration_timestamp",
"retreat_expiration_timestamp_padding",
"interest_rate",
"interest_rate_padding",
"nodes",
"volatility",
"volatility_padding",
"node_keys",
"halt_force_pricing",
"perp_update_timestamp",
"perp_funding_delta",
"perp_latest_funding_rate",
"perp_latest_midpoint",
"padding",
__pubkey, __slot
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24
) ON CONFLICT (
__pubkey
) DO UPDATE SET
"nonce" = EXCLUDED."nonce",
"mark_prices" = EXCLUDED."mark_prices",
"mark_prices_padding" = EXCLUDED."mark_prices_padding",
"perp_mark_price" = EXCLUDED."perp_mark_price",
"product_greeks" = EXCLUDED."product_greeks",
"product_greeks_padding" = EXCLUDED."product_greeks_padding",
"update_timestamp" = EXCLUDED."update_timestamp",
"update_timestamp_padding" = EXCLUDED."update_timestamp_padding",
"retreat_expiration_timestamp" = EXCLUDED."retreat_expiration_timestamp",
"retreat_expiration_timestamp_padding" = EXCLUDED."retreat_expiration_timestamp_padding",
"interest_rate" = EXCLUDED."interest_rate",
"interest_rate_padding" = EXCLUDED."interest_rate_padding",
"nodes" = EXCLUDED."nodes",
"volatility" = EXCLUDED."volatility",
"volatility_padding" = EXCLUDED."volatility_padding",
"node_keys" = EXCLUDED."node_keys",
"halt_force_pricing" = EXCLUDED."halt_force_pricing",
"perp_update_timestamp" = EXCLUDED."perp_update_timestamp",
"perp_funding_delta" = EXCLUDED."perp_funding_delta",
"perp_latest_funding_rate" = EXCLUDED."perp_latest_funding_rate",
"perp_latest_midpoint" = EXCLUDED."perp_latest_midpoint",
"padding" = EXCLUDED."padding",
__slot = EXCLUDED.__slot
"#)
.bind(self.nonce)
.bind(&self.mark_prices)
.bind(&self.mark_prices_padding)
.bind(&self.perp_mark_price)
.bind(&self.product_greeks)
.bind(&self.product_greeks_padding)
.bind(&self.update_timestamp)
.bind(&self.update_timestamp_padding)
.bind(&self.retreat_expiration_timestamp)
.bind(&self.retreat_expiration_timestamp_padding)
.bind(&self.interest_rate)
.bind(&self.interest_rate_padding)
.bind(&self.nodes)
.bind(&self.volatility)
.bind(&self.volatility_padding)
.bind(&self.node_keys)
.bind(&self.halt_force_pricing)
.bind(&self.perp_update_timestamp)
.bind(&self.perp_funding_delta)
.bind(&self.perp_latest_funding_rate)
.bind(&self.perp_latest_midpoint)
.bind(&self.padding)
.bind(self.account_metadata.pubkey)
.bind(&self.account_metadata.slot)
.execute(pool).await
.map_err(|e| carbon_core::error::Error::Custom(e.to_string()))?;
Ok(())
}
}
#[async_trait::async_trait]
impl carbon_core::postgres::operations::Delete for GreeksRow {
type Key = carbon_core::postgres::primitives::Pubkey;
async fn delete(key: Self::Key, pool: &sqlx::PgPool) -> carbon_core::error::CarbonResult<()> {
sqlx::query(
r#"DELETE FROM greeks_account WHERE
__pubkey = $1
"#,
)
.bind(key)
.execute(pool)
.await
.map_err(|e| carbon_core::error::Error::Custom(e.to_string()))?;
Ok(())
}
}
#[async_trait::async_trait]
impl carbon_core::postgres::operations::Lookup for GreeksRow {
type Key = carbon_core::postgres::primitives::Pubkey;
async fn lookup(
key: Self::Key,
pool: &sqlx::PgPool,
) -> carbon_core::error::CarbonResult<Option<Self>> {
let row = sqlx::query_as(
r#"SELECT * FROM greeks_account WHERE
__pubkey = $1
"#,
)
.bind(key)
.fetch_optional(pool)
.await
.map_err(|e| carbon_core::error::Error::Custom(e.to_string()))?;
Ok(row)
}
}
pub struct GreeksMigrationOperation;
#[async_trait::async_trait]
impl sqlx_migrator::Operation<sqlx::Postgres> for GreeksMigrationOperation {
async fn up(
&self,
connection: &mut sqlx::PgConnection,
) -> Result<(), sqlx_migrator::error::Error> {
sqlx::query(
r#"CREATE TABLE IF NOT EXISTS greeks_account (
-- Account data
"nonce" INT2 NOT NULL,
"mark_prices" NUMERIC(20)[] NOT NULL,
"mark_prices_padding" NUMERIC(20)[] NOT NULL,
"perp_mark_price" NUMERIC(20) NOT NULL,
"product_greeks" JSONB NOT NULL,
"product_greeks_padding" JSONB NOT NULL,
"update_timestamp" NUMERIC(20)[] NOT NULL,
"update_timestamp_padding" NUMERIC(20)[] NOT NULL,
"retreat_expiration_timestamp" NUMERIC(20)[] NOT NULL,
"retreat_expiration_timestamp_padding" NUMERIC(20)[] NOT NULL,
"interest_rate" INT8[] NOT NULL,
"interest_rate_padding" INT8[] NOT NULL,
"nodes" NUMERIC(20)[] NOT NULL,
"volatility" NUMERIC(20)[] NOT NULL,
"volatility_padding" NUMERIC(20)[] NOT NULL,
"node_keys" BYTEA[] NOT NULL,
"halt_force_pricing" BOOLEAN[] NOT NULL,
"perp_update_timestamp" NUMERIC(20) NOT NULL,
"perp_funding_delta" JSONB NOT NULL,
"perp_latest_funding_rate" JSONB NOT NULL,
"perp_latest_midpoint" NUMERIC(20) NOT NULL,
"padding" BYTEA NOT NULL,
-- Account metadata
__pubkey BYTEA NOT NULL,
__slot NUMERIC(20),
PRIMARY KEY (__pubkey)
)"#,
)
.execute(connection)
.await?;
Ok(())
}
async fn down(
&self,
connection: &mut sqlx::PgConnection,
) -> Result<(), sqlx_migrator::error::Error> {
sqlx::query(r#"DROP TABLE IF EXISTS greeks_account"#)
.execute(connection)
.await?;
Ok(())
}
}