use {
crate::types::HaltStateV2,
carbon_core::{
account::AccountMetadata,
postgres::{
metadata::AccountRowMetadata,
primitives::{Pubkey, U16, U32, U64, U8},
},
},
};
#[derive(sqlx::FromRow, Debug, Clone)]
pub struct StateRow {
#[sqlx(flatten)]
pub account_metadata: AccountRowMetadata,
pub admin: Pubkey,
pub state_nonce: U8,
pub serum_nonce: U8,
pub mint_auth_nonce: U8,
pub num_underlyings: U8,
pub num_flex_underlyings: U8,
pub null: Vec<u8>,
pub strike_initialization_threshold_seconds: U32,
pub pricing_frequency_seconds: U32,
pub liquidator_liquidation_percentage: U32,
pub insurance_vault_liquidation_percentage: U32,
pub native_d1_trade_fee_percentage: U64,
pub native_d1_underlying_fee_percentage: U64,
pub native_whitelist_underlying_fee_percentage: U64,
pub native_deposit_limit: U64,
pub expiration_threshold_seconds: U32,
pub position_movement_fee_bps: U8,
pub margin_concession_percentage: U8,
pub treasury_wallet_nonce: U8,
pub native_option_trade_fee_percentage: U64,
pub native_option_underlying_fee_percentage: U64,
pub referrals_admin: Pubkey,
pub referrals_rewards_wallet_nonce: U8,
pub max_perp_delta_age: U16,
pub secondary_admin: Pubkey,
pub vault_nonce: U8,
pub insurance_vault_nonce: U8,
pub deprecated_total_insurance_vault_deposits: U64,
pub native_withdraw_limit: U64,
pub withdraw_limit_epoch_seconds: U32,
pub native_open_interest_limit: U64,
pub halt_states: sqlx::types::Json<Vec<HaltStateV2>>,
pub halt_states_padding: sqlx::types::Json<Vec<HaltStateV2>>,
pub padding: Vec<u8>,
}
impl StateRow {
pub fn from_parts(source: crate::accounts::state::State, metadata: AccountMetadata) -> Self {
Self {
account_metadata: metadata.into(),
admin: source.admin.into(),
state_nonce: source.state_nonce.into(),
serum_nonce: source.serum_nonce.into(),
mint_auth_nonce: source.mint_auth_nonce.into(),
num_underlyings: source.num_underlyings.into(),
num_flex_underlyings: source.num_flex_underlyings.into(),
null: source.null.to_vec(),
strike_initialization_threshold_seconds: source
.strike_initialization_threshold_seconds
.into(),
pricing_frequency_seconds: source.pricing_frequency_seconds.into(),
liquidator_liquidation_percentage: source.liquidator_liquidation_percentage.into(),
insurance_vault_liquidation_percentage: source
.insurance_vault_liquidation_percentage
.into(),
native_d1_trade_fee_percentage: source.native_d1_trade_fee_percentage.into(),
native_d1_underlying_fee_percentage: source.native_d1_underlying_fee_percentage.into(),
native_whitelist_underlying_fee_percentage: source
.native_whitelist_underlying_fee_percentage
.into(),
native_deposit_limit: source.native_deposit_limit.into(),
expiration_threshold_seconds: source.expiration_threshold_seconds.into(),
position_movement_fee_bps: source.position_movement_fee_bps.into(),
margin_concession_percentage: source.margin_concession_percentage.into(),
treasury_wallet_nonce: source.treasury_wallet_nonce.into(),
native_option_trade_fee_percentage: source.native_option_trade_fee_percentage.into(),
native_option_underlying_fee_percentage: source
.native_option_underlying_fee_percentage
.into(),
referrals_admin: source.referrals_admin.into(),
referrals_rewards_wallet_nonce: source.referrals_rewards_wallet_nonce.into(),
max_perp_delta_age: source.max_perp_delta_age.into(),
secondary_admin: source.secondary_admin.into(),
vault_nonce: source.vault_nonce.into(),
insurance_vault_nonce: source.insurance_vault_nonce.into(),
deprecated_total_insurance_vault_deposits: source
.deprecated_total_insurance_vault_deposits
.into(),
native_withdraw_limit: source.native_withdraw_limit.into(),
withdraw_limit_epoch_seconds: source.withdraw_limit_epoch_seconds.into(),
native_open_interest_limit: source.native_open_interest_limit.into(),
halt_states: sqlx::types::Json(source.halt_states.to_vec()),
halt_states_padding: sqlx::types::Json(source.halt_states_padding.to_vec()),
padding: source.padding.to_vec(),
}
}
}
impl TryFrom<StateRow> for crate::accounts::state::State {
type Error = carbon_core::error::Error;
fn try_from(source: StateRow) -> Result<Self, Self::Error> {
Ok(Self {
admin: *source.admin,
state_nonce: source.state_nonce.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
serum_nonce: source.serum_nonce.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
mint_auth_nonce: source.mint_auth_nonce.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
num_underlyings: source.num_underlyings.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
num_flex_underlyings: source.num_flex_underlyings.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
null: source.null.as_slice().try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert padding from postgres primitive: expected 7 bytes"
.to_string(),
)
})?,
strike_initialization_threshold_seconds: source
.strike_initialization_threshold_seconds
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
pricing_frequency_seconds: source.pricing_frequency_seconds.try_into().map_err(
|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
},
)?,
liquidator_liquidation_percentage: source
.liquidator_liquidation_percentage
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
insurance_vault_liquidation_percentage: source
.insurance_vault_liquidation_percentage
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
native_d1_trade_fee_percentage: *source.native_d1_trade_fee_percentage,
native_d1_underlying_fee_percentage: *source.native_d1_underlying_fee_percentage,
native_whitelist_underlying_fee_percentage: *source
.native_whitelist_underlying_fee_percentage,
native_deposit_limit: *source.native_deposit_limit,
expiration_threshold_seconds: source.expiration_threshold_seconds.try_into().map_err(
|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
},
)?,
position_movement_fee_bps: source.position_movement_fee_bps.try_into().map_err(
|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
},
)?,
margin_concession_percentage: source.margin_concession_percentage.try_into().map_err(
|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
},
)?,
treasury_wallet_nonce: source.treasury_wallet_nonce.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
native_option_trade_fee_percentage: *source.native_option_trade_fee_percentage,
native_option_underlying_fee_percentage: *source
.native_option_underlying_fee_percentage,
referrals_admin: *source.referrals_admin,
referrals_rewards_wallet_nonce: source
.referrals_rewards_wallet_nonce
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
max_perp_delta_age: source.max_perp_delta_age.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
secondary_admin: *source.secondary_admin,
vault_nonce: source.vault_nonce.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
insurance_vault_nonce: source.insurance_vault_nonce.try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
deprecated_total_insurance_vault_deposits: *source
.deprecated_total_insurance_vault_deposits,
native_withdraw_limit: *source.native_withdraw_limit,
withdraw_limit_epoch_seconds: source.withdraw_limit_epoch_seconds.try_into().map_err(
|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
},
)?,
native_open_interest_limit: *source.native_open_interest_limit,
halt_states: source
.halt_states
.0
.into_iter()
.collect::<Vec<_>>()
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
halt_states_padding: source
.halt_states_padding
.0
.into_iter()
.collect::<Vec<_>>()
.try_into()
.map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert value from postgres primitive".to_string(),
)
})?,
padding: source.padding.as_slice().try_into().map_err(|_| {
carbon_core::error::Error::Custom(
"Failed to convert padding from postgres primitive: expected 338 bytes"
.to_string(),
)
})?,
})
}
}
impl carbon_core::postgres::operations::Table for crate::accounts::state::State {
fn table() -> &'static str {
"state_account"
}
fn columns() -> Vec<&'static str> {
vec![
"__pubkey",
"__slot",
"admin",
"state_nonce",
"serum_nonce",
"mint_auth_nonce",
"num_underlyings",
"num_flex_underlyings",
"null",
"strike_initialization_threshold_seconds",
"pricing_frequency_seconds",
"liquidator_liquidation_percentage",
"insurance_vault_liquidation_percentage",
"native_d1_trade_fee_percentage",
"native_d1_underlying_fee_percentage",
"native_whitelist_underlying_fee_percentage",
"native_deposit_limit",
"expiration_threshold_seconds",
"position_movement_fee_bps",
"margin_concession_percentage",
"treasury_wallet_nonce",
"native_option_trade_fee_percentage",
"native_option_underlying_fee_percentage",
"referrals_admin",
"referrals_rewards_wallet_nonce",
"max_perp_delta_age",
"secondary_admin",
"vault_nonce",
"insurance_vault_nonce",
"deprecated_total_insurance_vault_deposits",
"native_withdraw_limit",
"withdraw_limit_epoch_seconds",
"native_open_interest_limit",
"halt_states",
"halt_states_padding",
"padding",
]
}
}
#[async_trait::async_trait]
impl carbon_core::postgres::operations::Insert for StateRow {
async fn insert(&self, pool: &sqlx::PgPool) -> carbon_core::error::CarbonResult<()> {
sqlx::query(r#"
INSERT INTO state_account (
"admin",
"state_nonce",
"serum_nonce",
"mint_auth_nonce",
"num_underlyings",
"num_flex_underlyings",
"null",
"strike_initialization_threshold_seconds",
"pricing_frequency_seconds",
"liquidator_liquidation_percentage",
"insurance_vault_liquidation_percentage",
"native_d1_trade_fee_percentage",
"native_d1_underlying_fee_percentage",
"native_whitelist_underlying_fee_percentage",
"native_deposit_limit",
"expiration_threshold_seconds",
"position_movement_fee_bps",
"margin_concession_percentage",
"treasury_wallet_nonce",
"native_option_trade_fee_percentage",
"native_option_underlying_fee_percentage",
"referrals_admin",
"referrals_rewards_wallet_nonce",
"max_perp_delta_age",
"secondary_admin",
"vault_nonce",
"insurance_vault_nonce",
"deprecated_total_insurance_vault_deposits",
"native_withdraw_limit",
"withdraw_limit_epoch_seconds",
"native_open_interest_limit",
"halt_states",
"halt_states_padding",
"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, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36
)"#)
.bind(self.admin)
.bind(self.state_nonce)
.bind(self.serum_nonce)
.bind(self.mint_auth_nonce)
.bind(self.num_underlyings)
.bind(self.num_flex_underlyings)
.bind(&self.null)
.bind(self.strike_initialization_threshold_seconds)
.bind(self.pricing_frequency_seconds)
.bind(self.liquidator_liquidation_percentage)
.bind(self.insurance_vault_liquidation_percentage)
.bind(&self.native_d1_trade_fee_percentage)
.bind(&self.native_d1_underlying_fee_percentage)
.bind(&self.native_whitelist_underlying_fee_percentage)
.bind(&self.native_deposit_limit)
.bind(self.expiration_threshold_seconds)
.bind(self.position_movement_fee_bps)
.bind(self.margin_concession_percentage)
.bind(self.treasury_wallet_nonce)
.bind(&self.native_option_trade_fee_percentage)
.bind(&self.native_option_underlying_fee_percentage)
.bind(self.referrals_admin)
.bind(self.referrals_rewards_wallet_nonce)
.bind(self.max_perp_delta_age)
.bind(self.secondary_admin)
.bind(self.vault_nonce)
.bind(self.insurance_vault_nonce)
.bind(&self.deprecated_total_insurance_vault_deposits)
.bind(&self.native_withdraw_limit)
.bind(self.withdraw_limit_epoch_seconds)
.bind(&self.native_open_interest_limit)
.bind(&self.halt_states)
.bind(&self.halt_states_padding)
.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 StateRow {
async fn upsert(&self, pool: &sqlx::PgPool) -> carbon_core::error::CarbonResult<()> {
sqlx::query(r#"INSERT INTO state_account (
"admin",
"state_nonce",
"serum_nonce",
"mint_auth_nonce",
"num_underlyings",
"num_flex_underlyings",
"null",
"strike_initialization_threshold_seconds",
"pricing_frequency_seconds",
"liquidator_liquidation_percentage",
"insurance_vault_liquidation_percentage",
"native_d1_trade_fee_percentage",
"native_d1_underlying_fee_percentage",
"native_whitelist_underlying_fee_percentage",
"native_deposit_limit",
"expiration_threshold_seconds",
"position_movement_fee_bps",
"margin_concession_percentage",
"treasury_wallet_nonce",
"native_option_trade_fee_percentage",
"native_option_underlying_fee_percentage",
"referrals_admin",
"referrals_rewards_wallet_nonce",
"max_perp_delta_age",
"secondary_admin",
"vault_nonce",
"insurance_vault_nonce",
"deprecated_total_insurance_vault_deposits",
"native_withdraw_limit",
"withdraw_limit_epoch_seconds",
"native_open_interest_limit",
"halt_states",
"halt_states_padding",
"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, $25, $26, $27, $28, $29, $30, $31, $32, $33, $34, $35, $36
) ON CONFLICT (
__pubkey
) DO UPDATE SET
"admin" = EXCLUDED."admin",
"state_nonce" = EXCLUDED."state_nonce",
"serum_nonce" = EXCLUDED."serum_nonce",
"mint_auth_nonce" = EXCLUDED."mint_auth_nonce",
"num_underlyings" = EXCLUDED."num_underlyings",
"num_flex_underlyings" = EXCLUDED."num_flex_underlyings",
"null" = EXCLUDED."null",
"strike_initialization_threshold_seconds" = EXCLUDED."strike_initialization_threshold_seconds",
"pricing_frequency_seconds" = EXCLUDED."pricing_frequency_seconds",
"liquidator_liquidation_percentage" = EXCLUDED."liquidator_liquidation_percentage",
"insurance_vault_liquidation_percentage" = EXCLUDED."insurance_vault_liquidation_percentage",
"native_d1_trade_fee_percentage" = EXCLUDED."native_d1_trade_fee_percentage",
"native_d1_underlying_fee_percentage" = EXCLUDED."native_d1_underlying_fee_percentage",
"native_whitelist_underlying_fee_percentage" = EXCLUDED."native_whitelist_underlying_fee_percentage",
"native_deposit_limit" = EXCLUDED."native_deposit_limit",
"expiration_threshold_seconds" = EXCLUDED."expiration_threshold_seconds",
"position_movement_fee_bps" = EXCLUDED."position_movement_fee_bps",
"margin_concession_percentage" = EXCLUDED."margin_concession_percentage",
"treasury_wallet_nonce" = EXCLUDED."treasury_wallet_nonce",
"native_option_trade_fee_percentage" = EXCLUDED."native_option_trade_fee_percentage",
"native_option_underlying_fee_percentage" = EXCLUDED."native_option_underlying_fee_percentage",
"referrals_admin" = EXCLUDED."referrals_admin",
"referrals_rewards_wallet_nonce" = EXCLUDED."referrals_rewards_wallet_nonce",
"max_perp_delta_age" = EXCLUDED."max_perp_delta_age",
"secondary_admin" = EXCLUDED."secondary_admin",
"vault_nonce" = EXCLUDED."vault_nonce",
"insurance_vault_nonce" = EXCLUDED."insurance_vault_nonce",
"deprecated_total_insurance_vault_deposits" = EXCLUDED."deprecated_total_insurance_vault_deposits",
"native_withdraw_limit" = EXCLUDED."native_withdraw_limit",
"withdraw_limit_epoch_seconds" = EXCLUDED."withdraw_limit_epoch_seconds",
"native_open_interest_limit" = EXCLUDED."native_open_interest_limit",
"halt_states" = EXCLUDED."halt_states",
"halt_states_padding" = EXCLUDED."halt_states_padding",
"padding" = EXCLUDED."padding",
__slot = EXCLUDED.__slot
"#)
.bind(self.admin)
.bind(self.state_nonce)
.bind(self.serum_nonce)
.bind(self.mint_auth_nonce)
.bind(self.num_underlyings)
.bind(self.num_flex_underlyings)
.bind(&self.null)
.bind(self.strike_initialization_threshold_seconds)
.bind(self.pricing_frequency_seconds)
.bind(self.liquidator_liquidation_percentage)
.bind(self.insurance_vault_liquidation_percentage)
.bind(&self.native_d1_trade_fee_percentage)
.bind(&self.native_d1_underlying_fee_percentage)
.bind(&self.native_whitelist_underlying_fee_percentage)
.bind(&self.native_deposit_limit)
.bind(self.expiration_threshold_seconds)
.bind(self.position_movement_fee_bps)
.bind(self.margin_concession_percentage)
.bind(self.treasury_wallet_nonce)
.bind(&self.native_option_trade_fee_percentage)
.bind(&self.native_option_underlying_fee_percentage)
.bind(self.referrals_admin)
.bind(self.referrals_rewards_wallet_nonce)
.bind(self.max_perp_delta_age)
.bind(self.secondary_admin)
.bind(self.vault_nonce)
.bind(self.insurance_vault_nonce)
.bind(&self.deprecated_total_insurance_vault_deposits)
.bind(&self.native_withdraw_limit)
.bind(self.withdraw_limit_epoch_seconds)
.bind(&self.native_open_interest_limit)
.bind(&self.halt_states)
.bind(&self.halt_states_padding)
.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 StateRow {
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 state_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 StateRow {
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 state_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 StateMigrationOperation;
#[async_trait::async_trait]
impl sqlx_migrator::Operation<sqlx::Postgres> for StateMigrationOperation {
async fn up(
&self,
connection: &mut sqlx::PgConnection,
) -> Result<(), sqlx_migrator::error::Error> {
sqlx::query(
r#"CREATE TABLE IF NOT EXISTS state_account (
-- Account data
"admin" BYTEA NOT NULL,
"state_nonce" INT2 NOT NULL,
"serum_nonce" INT2 NOT NULL,
"mint_auth_nonce" INT2 NOT NULL,
"num_underlyings" INT2 NOT NULL,
"num_flex_underlyings" INT2 NOT NULL,
"null" BYTEA NOT NULL,
"strike_initialization_threshold_seconds" INT8 NOT NULL,
"pricing_frequency_seconds" INT8 NOT NULL,
"liquidator_liquidation_percentage" INT8 NOT NULL,
"insurance_vault_liquidation_percentage" INT8 NOT NULL,
"native_d1_trade_fee_percentage" NUMERIC(20) NOT NULL,
"native_d1_underlying_fee_percentage" NUMERIC(20) NOT NULL,
"native_whitelist_underlying_fee_percentage" NUMERIC(20) NOT NULL,
"native_deposit_limit" NUMERIC(20) NOT NULL,
"expiration_threshold_seconds" INT8 NOT NULL,
"position_movement_fee_bps" INT2 NOT NULL,
"margin_concession_percentage" INT2 NOT NULL,
"treasury_wallet_nonce" INT2 NOT NULL,
"native_option_trade_fee_percentage" NUMERIC(20) NOT NULL,
"native_option_underlying_fee_percentage" NUMERIC(20) NOT NULL,
"referrals_admin" BYTEA NOT NULL,
"referrals_rewards_wallet_nonce" INT2 NOT NULL,
"max_perp_delta_age" INT4 NOT NULL,
"secondary_admin" BYTEA NOT NULL,
"vault_nonce" INT2 NOT NULL,
"insurance_vault_nonce" INT2 NOT NULL,
"deprecated_total_insurance_vault_deposits" NUMERIC(20) NOT NULL,
"native_withdraw_limit" NUMERIC(20) NOT NULL,
"withdraw_limit_epoch_seconds" INT8 NOT NULL,
"native_open_interest_limit" NUMERIC(20) NOT NULL,
"halt_states" JSONB NOT NULL,
"halt_states_padding" JSONB 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 state_account"#)
.execute(connection)
.await?;
Ok(())
}
}